• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stefanberger / libtpms / #2022

23 Sep 2025 01:11PM UTC coverage: 77.227% (+0.009%) from 77.218%
#2022

push

travis-ci

web-flow
Merge a45c293de into 4504f47c6

36116 of 46766 relevant lines covered (77.23%)

125180.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.69
/src/tpm2/Session.c
1
/********************************************************************************/
2
/*                                                                                */
3
/*                            Manage the session context counter                         */
4
/*                             Written by Ken Goldman                                */
5
/*                       IBM Thomas J. Watson Research Center                        */
6
/*                                                                                */
7
/*  Licenses and Notices                                                        */
8
/*                                                                                */
9
/*  1. Copyright Licenses:                                                        */
10
/*                                                                                */
11
/*  - Trusted Computing Group (TCG) grants to the user of the source code in        */
12
/*    this specification (the "Source Code") a worldwide, irrevocable,                 */
13
/*    nonexclusive, royalty free, copyright license to reproduce, create         */
14
/*    derivative works, distribute, display and perform the Source Code and        */
15
/*    derivative works thereof, and to grant others the rights granted herein.        */
16
/*                                                                                */
17
/*  - The TCG grants to the user of the other parts of the specification         */
18
/*    (other than the Source Code) the rights to reproduce, distribute,         */
19
/*    display, and perform the specification solely for the purpose of                 */
20
/*    developing products based on such documents.                                */
21
/*                                                                                */
22
/*  2. Source Code Distribution Conditions:                                        */
23
/*                                                                                */
24
/*  - Redistributions of Source Code must retain the above copyright licenses,         */
25
/*    this list of conditions and the following disclaimers.                        */
26
/*                                                                                */
27
/*  - Redistributions in binary form must reproduce the above copyright         */
28
/*    licenses, this list of conditions        and the following disclaimers in the         */
29
/*    documentation and/or other materials provided with the distribution.        */
30
/*                                                                                */
31
/*  3. Disclaimers:                                                                */
32
/*                                                                                */
33
/*  - THE COPYRIGHT LICENSES SET FORTH ABOVE DO NOT REPRESENT ANY FORM OF        */
34
/*  LICENSE OR WAIVER, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, WITH        */
35
/*  RESPECT TO PATENT RIGHTS HELD BY TCG MEMBERS (OR OTHER THIRD PARTIES)        */
36
/*  THAT MAY BE NECESSARY TO IMPLEMENT THIS SPECIFICATION OR OTHERWISE.                */
37
/*  Contact TCG Administration (admin@trustedcomputinggroup.org) for                 */
38
/*  information on specification licensing rights available through TCG         */
39
/*  membership agreements.                                                        */
40
/*                                                                                */
41
/*  - THIS SPECIFICATION IS PROVIDED "AS IS" WITH NO EXPRESS OR IMPLIED         */
42
/*    WARRANTIES WHATSOEVER, INCLUDING ANY WARRANTY OF MERCHANTABILITY OR         */
43
/*    FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, COMPLETENESS, OR                 */
44
/*    NONINFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS, OR ANY WARRANTY                 */
45
/*    OTHERWISE ARISING OUT OF ANY PROPOSAL, SPECIFICATION OR SAMPLE.                */
46
/*                                                                                */
47
/*  - Without limitation, TCG and its members and licensors disclaim all         */
48
/*    liability, including liability for infringement of any proprietary         */
49
/*    rights, relating to use of information in this specification and to the        */
50
/*    implementation of this specification, and TCG disclaims all liability for        */
51
/*    cost of procurement of substitute goods or services, lost profits, loss         */
52
/*    of use, loss of data or any incidental, consequential, direct, indirect,         */
53
/*    or special damages, whether under contract, tort, warranty or otherwise,         */
54
/*    arising in any way out of use or reliance upon this specification or any         */
55
/*    information herein.                                                        */
56
/*                                                                                */
57
/*  (c) Copyright IBM Corp. and others, 2016 - 2023                                */
58
/*                                                                                */
59
/********************************************************************************/
60

61
//**Introduction
62
/*
63
    The code in this file is used to manage the session context counter.
64
    The scheme implemented here is a "truncated counter".
65
    This scheme allows the TPM to not need TPM_SU_CLEAR for a
66
    very long period of time and still not have the context
67
    count for a session repeated.
68

69
    The counter (contextCounter)in this implementation is a UINT64 but
70
    can be smaller.  The "tracking array" (contextArray) only
71
    has 16-bits per context.  The tracking array is the data
72
    that needs to be saved and restored across TPM_SU_STATE so that
73
    sessions are not lost when the system enters the sleep state.
74
    Also, when the TPM is active, the tracking array is kept in
75
    RAM making it important that the number of bytes for each
76
    entry be kept as small as possible.
77

78
    The TPM prevents "collisions" of these truncated values by
79
    not allowing a contextID to be assigned if it would be the
80
    same as an existing value.  Since the array holds 16 bits,
81
    after a context has been saved, an additional 2^16-1 contexts
82
    may be saved before the count would again match.  The normal
83
    expectation is that the context will be flushed before its count
84
    value is needed again but it is always possible to have long-lived
85
    sessions.
86

87
    The contextID is assigned when the context is saved (TPM2_ContextSave()).
88
    At that time, the TPM will compare the low-order 16 bits of
89
    contextCounter to the existing values in contextArray and if one
90
    matches, the TPM will return TPM_RC_CONTEXT_GAP (by construction,
91
    the entry that contains the matching value is the oldest
92
    context).
93

94
    The expected remediation by the TRM is to load the oldest saved
95
    session context (the one found by the TPM), and save it.  Since loading
96
    the oldest session also eliminates its contextID value from
97
    contextArray, there TPM will always be able to load and save the oldest
98
    existing context.
99

100
    In the worst case, software may have to load and save several contexts
101
    in order to save an additional one.  This should happen very infrequently.
102

103
    When the TPM searches contextArray and finds that none of the contextIDs
104
    match the low-order 16-bits of contextCount, the TPM can copy the low bits
105
    to the contextArray associated with the session, and increment contextCount.
106

107
    There is one entry in contextArray for each of the active sessions
108
    allowed by the TPM implementation.  This array contains either a
109
    context count, an index, or a value indicating the slot is available (0).
110

111
    The index into the contextArray is the handle for the session with the region
112
    selector byte of the session set to zero.  If an entry in contextArray contains
113
    0, then the corresponding handle may be assigned to a session.  If the entry
114
    contains a value that is less than or equal to the number of loaded sessions
115
    for the TPM, then the array entry is the slot in which the context is loaded.
116

117
    EXAMPLE:    If the TPM allows 8 loaded sessions, then the slot numbers would
118
    be 1-8 and a contextArrary value in that range would represent the loaded
119
    session.
120

121
    NOTE:   When the TPM firmware determines that the array entry is for a loaded
122
    session, it will subtract 1 to create the zero-based slot number.
123

124
    There is one significant corner case in this scheme.  When the contextCount
125
    is equal to a value in the contextArray, the oldest session needs to be
126
    recycled or flushed. In order to recycle the session, it must be loaded.
127
    To be loaded, there must be an available slot.  Rather than require that a
128
    spare slot be available all the time, the TPM will check to see if the
129
    contextCount is equal to some value in the contextArray when a session is
130
    created.  This prevents the last session slot from being used when it
131
    is likely that a session will need to be recycled.
132

133
    If a TPM with both 1.2 and 2.0 functionality uses this scheme for both
134
    1.2 and 2.0 sessions, and the list of active contexts is read with
135
    TPM_GetCapabiltiy(), the TPM will create 32-bit representations of the
136
    list that contains 16-bit values (the TPM2_GetCapability() returns a list
137
    of handles for active sessions rather than a list of contextID).  The full
138
    contextID has high-order bits that are either the same as the current
139
    contextCount or one less.  It is one less if the 16-bits
140
    of the contextArray has a value that is larger than the low-order 16 bits
141
    of contextCount.
142
*/
143

144
//** Includes, Defines, and Local Variables
145
#define SESSION_C
146
#include "Tpm.h"
147

148
//** File Scope Function -- ContextIdSetOldest()
149
/*
150
    This function is called when the oldest contextID is being loaded or deleted.
151
    Once a saved context becomes the oldest, it stays the oldest until it is
152
    deleted.
153

154
    Finding the oldest is a bit tricky.  It is not just the numeric comparison of
155
    values but is dependent on the value of contextCounter.
156

157
    Assume we have a small contextArray with 8, 4-bit values with values 1 and 2
158
    used to indicate the loaded context slot number.  Also assume that the array
159
    contains hex values of (0 0 1 0 3 0 9 F) and that the contextCounter is an
160
    8-bit counter with a value of 0x37. Since the low nibble is 7, that means
161
    that values above 7 are older than values below it and, in this example,
162
    9 is the oldest value.
163

164
    Note if we subtract the counter value, from each slot that contains a saved
165
    contextID we get (- - - - B - 2 - 8) and the oldest entry is now easy to find.
166
*/
167
static void ContextIdSetOldest(void)
34✔
168
{
169
    CONTEXT_SLOT lowBits;
34✔
170
    CONTEXT_SLOT entry;
34✔
171
    CONTEXT_SLOT smallest = CONTEXT_SLOT_MASKED(~0);        // libtpms changed
34✔
172
    UINT32       i;
34✔
173
    pAssert(s_ContextSlotMask == 0xff || s_ContextSlotMask == 0xffff); // libtpms added
34✔
174

175
    // Set oldestSaveContext to a value indicating none assigned
176
    s_oldestSavedSession = MAX_ACTIVE_SESSIONS + 1;
34✔
177
    lowBits = CONTEXT_SLOT_MASKED(gr.contextCounter);        // libtpms changed
34✔
178
    for(i = 0; i < MAX_ACTIVE_SESSIONS; i++)
2,210✔
179
    {
180
        entry = gr.contextArray[i];
2,176✔
181

182
        // only look at entries that are saved contexts
183
        if(entry > MAX_LOADED_SESSIONS)
2,176✔
184
        {
185
            // Use a less than or equal in case the oldest
186
            // is brand new (= lowBits-1) and equal to our initial
187
            // value for smallest.
188
            if(CONTEXT_SLOT_MASKED(entry - lowBits) <= smallest)        // libtpms changed
2✔
189
            {
190
                smallest             = CONTEXT_SLOT_MASKED(entry - lowBits);        // libtpms changed
2✔
191
                s_oldestSavedSession = i;
2✔
192
            }
193
        }
194
    }
195
    // When we finish, either the s_oldestSavedSession still has its initial
196
    // value, or it has the index of the oldest saved context.
197
}
34✔
198

199
//** Startup Function -- SessionStartup()
200
// This function initializes the session subsystem on TPM2_Startup().
201
BOOL SessionStartup(STARTUP_TYPE type)
4,251✔
202
{
203
    UINT32 i;
4,251✔
204

205
    // Initialize session slots.  At startup, all the in-memory session slots
206
    // are cleared and marked as not occupied
207
    for(i = 0; i < MAX_LOADED_SESSIONS; i++)
17,004✔
208
        s_sessions[i].occupied = FALSE;  // session slot is not occupied
12,753✔
209

210
    // The free session slots the number of maximum allowed loaded sessions
211
    s_freeSessionSlots = MAX_LOADED_SESSIONS;
4,251✔
212

213
    // Initialize context ID data.  On a ST_SAVE or hibernate sequence, it will
214
    // scan the saved array of session context counts, and clear any entry that
215
    // references a session that was in memory during the state save since that
216
    // memory was not preserved over the ST_SAVE.
217
    if(type == SU_RESUME || type == SU_RESTART)
4,251✔
218
    {
219
        // On ST_SAVE we preserve the contexts that were saved but not the ones
220
        // in memory
221
        for(i = 0; i < MAX_ACTIVE_SESSIONS; i++)
2,015✔
222
        {
223
            // If the array value is unused or references a loaded session then
224
            // that loaded session context is lost and the array entry is
225
            // reclaimed.
226
            if(gr.contextArray[i] <= MAX_LOADED_SESSIONS)
1,984✔
227
                gr.contextArray[i] = 0;
1,982✔
228
        }
229
        // Find the oldest session in context ID data and set it in
230
        // s_oldestSavedSession
231
        ContextIdSetOldest();
31✔
232
    }
233
    else
234
    {
235
        // For STARTUP_CLEAR, clear out the contextArray
236
        for(i = 0; i < MAX_ACTIVE_SESSIONS; i++)
274,300✔
237
            gr.contextArray[i] = 0;
270,080✔
238

239
        // reset the context counter
240
        gr.contextCounter = MAX_LOADED_SESSIONS + 1;
4,220✔
241

242
        // Initialize oldest saved session
243
        s_oldestSavedSession = MAX_ACTIVE_SESSIONS + 1;
4,220✔
244

245
       // Initialize the context slot mask for UINT16
246
       s_ContextSlotMask = 0xffff;        // libtpms added
4,220✔
247
    }
248
    return TRUE;
4,251✔
249
}
250

251
//************************************************
252
//** Access Functions
253
//************************************************
254

255
//*** SessionIsLoaded()
256
// This function test a session handle references a loaded session.  The handle
257
// must have previously been checked to make sure that it is a valid handle for
258
// an authorization session.
259
// NOTE:    A PWAP authorization does not have a session.
260
//
261
//  Return Type: BOOL
262
//      TRUE(1)         session is loaded
263
//      FALSE(0)        session is not loaded
264
//
265
BOOL SessionIsLoaded(TPM_HANDLE handle  // IN: session handle
2,473✔
266
)
267
{
268
    pAssert(HandleGetType(handle) == TPM_HT_POLICY_SESSION
2,473✔
269
            || HandleGetType(handle) == TPM_HT_HMAC_SESSION);
270

271
    handle = handle & HR_HANDLE_MASK;
2,473✔
272

273
    // if out of range of possible active session, or not assigned to a loaded
274
    // session return false
275
    if(handle >= MAX_ACTIVE_SESSIONS || gr.contextArray[handle] == 0
2,473✔
276
       || gr.contextArray[handle] > MAX_LOADED_SESSIONS)
2,382✔
277
        return FALSE;
92✔
278

279
    return TRUE;
280
}
281

282
//*** SessionIsSaved()
283
// This function test a session handle references a saved session.  The handle
284
// must have previously been checked to make sure that it is a valid handle for
285
// an authorization session.
286
// NOTE:    An password authorization does not have a session.
287
//
288
// This function requires that the handle be a valid session handle.
289
//
290
//  Return Type: BOOL
291
//      TRUE(1)         session is saved
292
//      FALSE(0)        session is not saved
293
//
294
BOOL SessionIsSaved(TPM_HANDLE handle  // IN: session handle
9✔
295
)
296
{
297
    pAssert(HandleGetType(handle) == TPM_HT_POLICY_SESSION
9✔
298
            || HandleGetType(handle) == TPM_HT_HMAC_SESSION);
299

300
    handle = handle & HR_HANDLE_MASK;
9✔
301
    // if out of range of possible active session, or not assigned, or
302
    // assigned to a loaded session, return false
303
    if(handle >= MAX_ACTIVE_SESSIONS || gr.contextArray[handle] == 0
9✔
304
       || gr.contextArray[handle] <= MAX_LOADED_SESSIONS)
×
305
        return FALSE;
9✔
306

307
    return TRUE;
308
}
309

310
//*** SequenceNumberForSavedContextIsValid()
311
// This function validates that the sequence number and handle value within a
312
// saved context are valid.
313
BOOL SequenceNumberForSavedContextIsValid(
3✔
314
    TPMS_CONTEXT* context  // IN: pointer to a context structure to be
315
                           //     validated
316
)
317
{
318
#define MAX_CONTEXT_GAP ((UINT64)(CONTEXT_SLOT_MASKED(~0) + 1)) /* libtpms changed */
319
    pAssert(s_ContextSlotMask == 0xff || s_ContextSlotMask == 0xffff); // libtpms added
3✔
320

321
    TPM_HANDLE handle = context->savedHandle & HR_HANDLE_MASK;
3✔
322

323
    if(  // Handle must be with the range of active sessions
3✔
324
        handle >= MAX_ACTIVE_SESSIONS
325
        // the array entry must be for a saved context
326
        || gr.contextArray[handle] <= MAX_LOADED_SESSIONS
3✔
327
        // the array entry must agree with the sequence number
328
        || gr.contextArray[handle] != CONTEXT_SLOT_MASKED(context->sequence) // libtpms changed
3✔
329
        // the provided sequence number has to be less than the current counter
330
        || context->sequence > gr.contextCounter
3✔
331
        // but not so much that it could not be a valid sequence number
332
        || gr.contextCounter - context->sequence > MAX_CONTEXT_GAP)
3✔
333
        return FALSE;
×
334

335
    return TRUE;
336
}
337

338
//*** SessionPCRValueIsCurrent()
339
//
340
// This function is used to check if PCR values have been updated since the
341
// last time they were checked in a policy session.
342
//
343
// This function requires the session is loaded.
344
//  Return Type: BOOL
345
//      TRUE(1)         PCR value is current
346
//      FALSE(0)        PCR value is not current
347
BOOL SessionPCRValueIsCurrent(SESSION* session  // IN: session structure
208✔
348
)
349
{
350
    if(session->pcrCounter != 0 && session->pcrCounter != gr.pcrCounter)
208✔
351
        return FALSE;
352
    else
353
        return TRUE;
207✔
354
}
355

356
//*** SessionGet()
357
// This function returns a pointer to the session object associated with a
358
// session handle.
359
//
360
// The function requires that the session is loaded.
361
SESSION* SessionGet(TPM_HANDLE handle  // IN: session handle
8,981✔
362
)
363
{
364
    size_t       slotIndex;
8,981✔
365
    CONTEXT_SLOT sessionIndex;
8,981✔
366

367
    pAssert(HandleGetType(handle) == TPM_HT_POLICY_SESSION
8,981✔
368
            || HandleGetType(handle) == TPM_HT_HMAC_SESSION);
369

370
    slotIndex = handle & HR_HANDLE_MASK;
8,981✔
371

372
    pAssert(slotIndex < MAX_ACTIVE_SESSIONS);
8,981✔
373

374
    // get the contents of the session array.  Because session is loaded, we
375
    // should always get a valid sessionIndex
376
    sessionIndex = gr.contextArray[slotIndex] - 1;
8,981✔
377

378
    pAssert(sessionIndex < MAX_LOADED_SESSIONS);
8,981✔
379

380
    return &s_sessions[sessionIndex].session;
8,981✔
381
}
382

383
//************************************************
384
//** Utility Functions
385
//************************************************
386

387
//*** ContextIdSessionCreate()
388
//
389
//  This function is called when a session is created.  It will check
390
//  to see if the current gap would prevent a context from being saved.  If
391
//  so it will return TPM_RC_CONTEXT_GAP.  Otherwise, it will try to find
392
//  an open slot in contextArray, set contextArray to the slot.
393
//
394
//  This routine requires that the caller has determined the session array
395
//  index for the session.
396
//
397
//  Return Type: TPM_RC
398
//      TPM_RC_CONTEXT_GAP      can't assign a new contextID until the oldest
399
//                              saved session context is recycled
400
//      TPM_RC_SESSION_HANDLE   there is no slot available in the context array
401
//                              for tracking of this session context
402
static TPM_RC ContextIdSessionCreate(
428✔
403
    TPM_HANDLE* handle,  // OUT: receives the assigned handle. This will
404
                         //     be an index that must be adjusted by the
405
                         //     caller according to the type of the
406
                         //     session created
407
    UINT32 sessionIndex  // IN: The session context array entry that will
408
                         //     be occupied by the created session
409
)
410
{
411
    pAssert(sessionIndex < MAX_LOADED_SESSIONS);
428✔
412

413
    // check to see if creating the context is safe
414
    // Is this going to be an assignment for the last session context
415
    // array entry?  If so, then there will be no room to recycle the
416
    // oldest context if needed.  If the gap is not at maximum, then
417
    // it will be possible to save a context if it becomes necessary.
418
    if(s_oldestSavedSession < MAX_ACTIVE_SESSIONS && s_freeSessionSlots == 1)
428✔
419
    {
420
        // See if the gap is at maximum
421
        // The current value of the contextCounter will be assigned to the next
422
        // saved context. If the value to be assigned would make the same as an
423
        // existing context, then we can't use it because of the ambiguity it would
424
        // create.
425
        if(CONTEXT_SLOT_MASKED(gr.contextCounter) // libtpms changed
×
426
           == gr.contextArray[s_oldestSavedSession])
×
427
            return TPM_RC_CONTEXT_GAP;
428
    }
429

430
    // Find an unoccupied entry in the contextArray
431
    for(*handle = 0; *handle < MAX_ACTIVE_SESSIONS; (*handle)++)
481✔
432
    {
433
        if(gr.contextArray[*handle] == 0)
481✔
434
        {
435
            // indicate that the session associated with this handle
436
            // references a loaded session
437
            gr.contextArray[*handle] = CONTEXT_SLOT_MASKED(sessionIndex + 1); // libtpms changed
428✔
438
            return TPM_RC_SUCCESS;
428✔
439
        }
440
    }
441
    return TPM_RC_SESSION_HANDLES;
442
}
443

444
//*** SessionCreate()
445
//
446
//  This function does the detailed work for starting an authorization session.
447
//  This is done in a support routine rather than in the action code because
448
//  the session management may differ in implementations.  This implementation
449
//  uses a fixed memory allocation to hold sessions and a fixed allocation
450
//  to hold the contextID for the saved contexts.
451
//
452
//  Return Type: TPM_RC
453
//      TPM_RC_CONTEXT_GAP          need to recycle sessions
454
//      TPM_RC_SESSION_HANDLE       active session space is full
455
//      TPM_RC_SESSION_MEMORY       loaded session space is full
456
TPM_RC
457
SessionCreate(TPM_SE         sessionType,    // IN: the session type
428✔
458
              TPMI_ALG_HASH  authHash,       // IN: the hash algorithm
459
              TPM2B_NONCE*   nonceCaller,    // IN: initial nonceCaller
460
              TPMT_SYM_DEF*  symmetric,      // IN: the symmetric algorithm
461
              TPMI_DH_ENTITY bind,           // IN: the bind object
462
              TPM2B_DATA*    seed,           // IN: seed data
463
              TPM_HANDLE*    sessionHandle,  // OUT: the session handle
464
              TPM2B_NONCE*   nonceTpm        // OUT: the session nonce
465
)
466
{
467
    TPM_RC       result = TPM_RC_SUCCESS;
428✔
468
    CONTEXT_SLOT slotIndex;
428✔
469
    SESSION*     session = NULL;
428✔
470

471
    pAssert(sessionType == TPM_SE_HMAC || sessionType == TPM_SE_POLICY
428✔
472
            || sessionType == TPM_SE_TRIAL);
473

474
    // If there are no open spots in the session array, then no point in searching
475
    if(s_freeSessionSlots == 0)
428✔
476
        return TPM_RC_SESSION_MEMORY;
477

478
    // Find a space for loading a session
479
    for(slotIndex = 0; slotIndex < MAX_LOADED_SESSIONS; slotIndex++)
481✔
480
    {
481
        // Is this available?
482
        if(s_sessions[slotIndex].occupied == FALSE)
481✔
483
        {
484
            session = &s_sessions[slotIndex].session;
428✔
485
            break;
428✔
486
        }
487
    }
488
    // if no spot found, then this is an internal error
489
    if(slotIndex >= MAX_LOADED_SESSIONS) {                // libtpms changed
428✔
490
        FAIL(FATAL_ERROR_INTERNAL);
×
491
        // should never get here due to longjmp        in FAIL()  libtpms added begin; cppcheck
492
        return TPM_RC_FAILURE;
493
    }                                                        // libtpms added end
494
    // Call context ID function to get a handle.  TPM_RC_SESSION_HANDLE may be
495
    // returned from ContextIdHandelAssign()
496
    result = ContextIdSessionCreate(sessionHandle, slotIndex);
428✔
497
    if(result != TPM_RC_SUCCESS)
428✔
498
        return result;
499

500
    //*** Only return from this point on is TPM_RC_SUCCESS
501

502
    // Can now indicate that the session array entry is occupied.
503
    s_freeSessionSlots--;
428✔
504
    s_sessions[slotIndex].occupied = TRUE;
428✔
505

506
    // Initialize the session data
507
    MemorySet(session, 0, sizeof(SESSION));
428✔
508

509
    // Initialize internal session data
510
    session->authHashAlg = authHash;
428✔
511
    // Initialize session type
512
    if(sessionType == TPM_SE_HMAC)
428✔
513
    {
514
        *sessionHandle += HMAC_SESSION_FIRST;
176✔
515
    }
516
    else
517
    {
518
        *sessionHandle += POLICY_SESSION_FIRST;
252✔
519

520
        // For TPM_SE_POLICY or TPM_SE_TRIAL
521
        session->attributes.isPolicy = SET;
252✔
522
        if(sessionType == TPM_SE_TRIAL)
252✔
523
            session->attributes.isTrialPolicy = SET;
5✔
524

525
        SessionSetStartTime(session);
252✔
526

527
        // Initialize policyDigest.  policyDigest is initialized with a string of 0
528
        // of session algorithm digest size. Since the session is already clear.
529
        // Just need to set the size
530
        session->u2.policyDigest.t.size =
252✔
531
            CryptHashGetDigestSize(session->authHashAlg);
252✔
532
    }
533
    // Create initial session nonce
534
    session->nonceTPM.t.size = nonceCaller->t.size;
428✔
535
    CryptRandomGenerate(session->nonceTPM.t.size, session->nonceTPM.t.buffer);
428✔
536
    MemoryCopy2B(&nonceTpm->b, &session->nonceTPM.b, sizeof(nonceTpm->t.buffer));
428✔
537

538
    // Set up session parameter encryption algorithm
539
    session->symmetric = *symmetric;
428✔
540

541
    // If there is a bind object or a session secret, then need to compute
542
    // a sessionKey.
543
    if(bind != TPM_RH_NULL || seed->t.size != 0)
428✔
544
    {
545
        // sessionKey = KDFa(hash, (authValue || seed), "ATH", nonceTPM,
546
        //                      nonceCaller, bits)
547
        // The HMAC key for generating the sessionSecret can be the concatenation
548
        // of an authorization value and a seed value
549
        TPM2B_TYPE(KEY, (sizeof(TPMT_HA) + sizeof(seed->t.buffer)));
112✔
550
        TPM2B_KEY key;
112✔
551

552
        // Get hash size, which is also the length of sessionKey
553
        session->sessionKey.t.size = CryptHashGetDigestSize(session->authHashAlg);
112✔
554

555
        // Get authValue of associated entity
556
        EntityGetAuthValue(bind, (TPM2B_AUTH*)&key);
112✔
557
        pAssert(key.t.size + seed->t.size <= sizeof(key.t.buffer));
112✔
558

559
        // Concatenate authValue and seed
560
        MemoryConcat2B(&key.b, &seed->b, sizeof(key.t.buffer));
112✔
561

562
        // Compute the session key
563
        CryptKDFa(session->authHashAlg,
112✔
564
                  &key.b,
565
                  SESSION_KEY,
566
                  &session->nonceTPM.b,
567
                  &nonceCaller->b,
112✔
568
                  session->sessionKey.t.size * 8,
112✔
569
                  session->sessionKey.t.buffer,
112✔
570
                  NULL,
571
                  FALSE);
572
    }
573

574
    // Copy the name of the entity that the HMAC session is bound to
575
    // Policy session is not bound to an entity
576
    if(bind != TPM_RH_NULL && sessionType == TPM_SE_HMAC)
428✔
577
    {
578
        session->attributes.isBound = SET;
48✔
579
        SessionComputeBoundEntity(bind, &session->u1.boundEntity);
48✔
580
    }
581
    // If there is a bind object and it is subject to DA, then use of this session
582
    // is subject to DA regardless of how it is used.
583
    session->attributes.isDaBound = (bind != TPM_RH_NULL)
856✔
584
                                    && (IsDAExempted(bind) == FALSE);
428✔
585

586
    // If the session is bound, then check to see if it is bound to lockoutAuth
587
    session->attributes.isLockoutBound = (session->attributes.isDaBound == SET)
856✔
588
                                         && (bind == TPM_RH_LOCKOUT);
428✔
589
    return TPM_RC_SUCCESS;
428✔
590
}
591

592
//*** SessionContextSave()
593
// This function is called when a session context is to be saved.  The
594
// contextID of the saved session is returned.  If no contextID can be
595
// assigned, then the routine returns TPM_RC_CONTEXT_GAP.
596
// If the function completes normally, the session slot will be freed.
597
//
598
// This function requires that 'handle' references a loaded session.
599
// Otherwise, it should not be called at the first place.
600
//
601
//  Return Type: TPM_RC
602
//      TPM_RC_CONTEXT_GAP              a contextID could not be assigned
603
//      TPM_RC_TOO_MANY_CONTEXTS        the counter maxed out
604
//
605
TPM_RC
606
SessionContextSave(TPM_HANDLE       handle,    // IN: session handle
4✔
607
                   CONTEXT_COUNTER* contextID  // OUT: assigned contextID
608
)
609
{
610
    UINT32       contextIndex;
4✔
611
    CONTEXT_SLOT slotIndex;
4✔
612

613
    pAssert(SessionIsLoaded(handle));
4✔
614
    pAssert(s_ContextSlotMask == 0xff || s_ContextSlotMask == 0xffff); // libtpms added
4✔
615

616
    // check to see if the gap is already maxed out
617
    // Need to have a saved session
618
    if(s_oldestSavedSession < MAX_ACTIVE_SESSIONS
4✔
619
       // if the oldest saved session has the same value as the low bits
620
       // of the contextCounter, then the GAP is maxed out.
621
       && gr.contextArray[s_oldestSavedSession] == CONTEXT_SLOT_MASKED(gr.contextCounter)) // libtpms changed
×
622
        return TPM_RC_CONTEXT_GAP;
623

624
    // if the caller wants the context counter, set it
625
    if(contextID != NULL)
4✔
626
        *contextID = gr.contextCounter;
4✔
627

628
    contextIndex = handle & HR_HANDLE_MASK;
4✔
629
    pAssert(contextIndex < MAX_ACTIVE_SESSIONS);
4✔
630

631
    // Extract the session slot number referenced by the contextArray
632
    // because we are going to overwrite this with the low order
633
    // contextID value.
634
    slotIndex = gr.contextArray[contextIndex] - 1;
4✔
635

636
    // Set the contextID for the contextArray
637
    gr.contextArray[contextIndex] = CONTEXT_SLOT_MASKED(gr.contextCounter); // libtpms changed
4✔
638

639
    // Increment the counter
640
    gr.contextCounter++;
4✔
641

642
    // In the unlikely event that the 64-bit context counter rolls over...
643
    if(gr.contextCounter == 0)
4✔
644
    {
645
        // back it up
646
        gr.contextCounter--;
×
647
        // return an error
648
        return TPM_RC_TOO_MANY_CONTEXTS;
×
649
    }
650
    // if the low-order bits wrapped, need to advance the value to skip over
651
    // the values used to indicate that a session is loaded
652
    if(CONTEXT_SLOT_MASKED(gr.contextCounter) == 0) // libtpms changed
4✔
653
        gr.contextCounter += MAX_LOADED_SESSIONS + 1;
×
654

655
    // If no other sessions are saved, this is now the oldest.
656
    if(s_oldestSavedSession >= MAX_ACTIVE_SESSIONS)
4✔
657
        s_oldestSavedSession = contextIndex;
4✔
658

659
    // Mark the session slot as unoccupied
660
    s_sessions[slotIndex].occupied = FALSE;
4✔
661

662
    // and indicate that there is an additional open slot
663
    s_freeSessionSlots++;
4✔
664

665
    return TPM_RC_SUCCESS;
4✔
666
}
667

668
//*** SessionContextLoad()
669
// This function is used to load a session from saved context.  The session
670
// handle must be for a saved context.
671
//
672
// If the gap is at a maximum, then the only session that can be loaded is
673
// the oldest session, otherwise TPM_RC_CONTEXT_GAP is returned.
674
//
675
// This function requires that 'handle' references a valid saved session.
676
//
677
//  Return Type: TPM_RC
678
//      TPM_RC_SESSION_MEMORY       no free session slots
679
//      TPM_RC_CONTEXT_GAP          the gap count is maximum and this
680
//                                  is not the oldest saved context
681
//
682
TPM_RC
683
SessionContextLoad(SESSION_BUF* session,  // IN: session structure from saved context
3✔
684
                   TPM_HANDLE*  handle    // IN/OUT: session handle
685
)
686
{
687
    UINT32       contextIndex;
3✔
688
    CONTEXT_SLOT slotIndex;
3✔
689

690
    pAssert(s_ContextSlotMask == 0xff || s_ContextSlotMask == 0xffff); // libtpms added
3✔
691
    pAssert(HandleGetType(*handle) == TPM_HT_POLICY_SESSION
3✔
692
            || HandleGetType(*handle) == TPM_HT_HMAC_SESSION);
693

694
    // Don't bother looking if no openings
695
    if(s_freeSessionSlots == 0)
3✔
696
        return TPM_RC_SESSION_MEMORY;
697

698
    // Find a free session slot to load the session
699
    for(slotIndex = 0; slotIndex < MAX_LOADED_SESSIONS; slotIndex++)
3✔
700
        if(s_sessions[slotIndex].occupied == FALSE)
3✔
701
            break;
702

703
    // if no spot found, then this is an internal error
704
    pAssert(slotIndex < MAX_LOADED_SESSIONS);
3✔
705

706
    // libtpms: besides the s_freeSessionSlots guard add another array index guard
707
    if (slotIndex >= MAX_LOADED_SESSIONS) {        // libtpms added begin; cppcheck
3✔
708
        FAIL(FATAL_ERROR_INTERNAL);
709
        // should never get here due to longjmp        in FAIL()
710
        return TPM_RC_FAILURE;
711
    }                                                // libtpms added end
712
    contextIndex = *handle & HR_HANDLE_MASK;  // extract the index
3✔
713

714
    // If there is only one slot left, and the gap is at maximum, the only session
715
    // context that we can safely load is the oldest one.
716
    if(s_oldestSavedSession < MAX_ACTIVE_SESSIONS && s_freeSessionSlots == 1
3✔
717
       && CONTEXT_SLOT_MASKED(gr.contextCounter) == gr.contextArray[s_oldestSavedSession] // libtpms changed
×
718
       && contextIndex != s_oldestSavedSession)
×
719
        return TPM_RC_CONTEXT_GAP;
720

721
    pAssert(contextIndex < MAX_ACTIVE_SESSIONS);
3✔
722

723
    // set the contextArray value to point to the session slot where
724
    // the context is loaded
725
    gr.contextArray[contextIndex] = slotIndex + 1;
3✔
726

727
    // if this was the oldest context, find the new oldest
728
    if(contextIndex == s_oldestSavedSession)
3✔
729
        ContextIdSetOldest();
3✔
730

731
    // Copy session data to session slot
732
    MemoryCopy(&s_sessions[slotIndex].session, session, sizeof(SESSION));
3✔
733

734
    // Set session slot as occupied
735
    s_sessions[slotIndex].occupied = TRUE;
3✔
736

737
    // Reduce the number of open spots
738
    s_freeSessionSlots--;
3✔
739

740
    return TPM_RC_SUCCESS;
3✔
741
}
742

743
//*** SessionFlush()
744
// This function is used to flush a session referenced by its handle.  If the
745
// session associated with 'handle' is loaded, the session array entry is
746
// marked as available.
747
//
748
// This function requires that 'handle' be a valid active session.
749
//
750
void SessionFlush(TPM_HANDLE handle  // IN: loaded or saved session handle
389✔
751
)
752
{
753
    CONTEXT_SLOT slotIndex;
389✔
754
    UINT32       contextIndex;  // Index into contextArray
389✔
755

756
    pAssert((HandleGetType(handle) == TPM_HT_POLICY_SESSION
389✔
757
             || HandleGetType(handle) == TPM_HT_HMAC_SESSION)
758
            && (SessionIsLoaded(handle) || SessionIsSaved(handle)));
759

760
    // Flush context ID of this session
761
    // Convert handle to an index into the contextArray
762
    contextIndex = handle & HR_HANDLE_MASK;
389✔
763

764
    pAssert(contextIndex < sizeof(gr.contextArray) / sizeof(gr.contextArray[0]));
389✔
765

766
    // Get the current contents of the array
767
    slotIndex = gr.contextArray[contextIndex];
389✔
768

769
    // Mark context array entry as available
770
    gr.contextArray[contextIndex] = 0;
389✔
771

772
    // Is this a saved session being flushed
773
    if(slotIndex > MAX_LOADED_SESSIONS)
389✔
774
    {
775
        // Flushing the oldest session?
776
        if(contextIndex == s_oldestSavedSession)
×
777
            // If so, find a new value for oldest.
778
            ContextIdSetOldest();
×
779
    }
780
    else
781
    {
782
        // Adjust slot index to point to session array index
783
        slotIndex -= 1;
389✔
784

785
        // Free session array index
786
        s_sessions[slotIndex].occupied = FALSE;
389✔
787
        s_freeSessionSlots++;
389✔
788
    }
789

790
    return;
389✔
791
}
792

793
//*** SessionComputeBoundEntity()
794
// This function computes the binding value for a session.  The binding value
795
// for a reserved handle is the handle itself.  For all the other entities,
796
// the authValue at the time of binding is included to prevent squatting.
797
// For those values, the Name and the authValue are concatenated
798
// into the bind buffer.  If they will not both fit, the will be overlapped
799
// by XORing bytes.  If XOR is required, the bind value will be full.
800
void SessionComputeBoundEntity(TPMI_DH_ENTITY entityHandle,  // IN: handle of entity
108✔
801
                               TPM2B_NAME*    bind           // OUT: binding value
802
)
803
{
804
    TPM2B_AUTH auth;
108✔
805
    BYTE*      pAuth = auth.t.buffer;
108✔
806
    UINT16     i;
108✔
807

808
    // Get name
809
    EntityGetName(entityHandle, bind);
108✔
810

811
    //    // The bound value of a reserved handle is the handle itself
812
    //    if(bind->t.size == sizeof(TPM_HANDLE)) return;
813

814
    // For all the other entities, concatenate the authorization value to the name.
815
    // Get a local copy of the authorization value because some overlapping
816
    // may be necessary.
817
    EntityGetAuthValue(entityHandle, &auth);
108✔
818

819
    // Make sure that the extra space is zeroed
820
    MemorySet(&bind->t.name[bind->t.size], 0, sizeof(bind->t.name) - bind->t.size);
108✔
821
    // XOR the authValue at the end of the name
822
    for(i = sizeof(bind->t.name) - auth.t.size; i < sizeof(bind->t.name); i++)
360✔
823
        bind->t.name[i] ^= *pAuth++;
252✔
824

825
    // Set the bind value to the maximum size
826
    bind->t.size = sizeof(bind->t.name);
108✔
827

828
    return;
108✔
829
}
830

831
//*** SessionSetStartTime()
832
// This function is used to initialize the session timing
833
void SessionSetStartTime(SESSION* session  // IN: the session to update
373✔
834
)
835
{
836
    session->startTime = g_time;
373✔
837
    session->epoch     = g_timeEpoch;
373✔
838
    session->timeout   = 0;
373✔
839
}
373✔
840

841
//*** SessionResetPolicyData()
842
// This function is used to reset the policy data without changing the nonce
843
// or the start time of the session.
844
void SessionResetPolicyData(SESSION* session  // IN: the session to reset
167✔
845
)
846
{
847
    SESSION_ATTRIBUTES oldAttributes;
167✔
848
    pAssert(session != NULL);
167✔
849

850
    // Will need later
851
    oldAttributes = session->attributes;
167✔
852

853
    // No command
854
    session->commandCode = 0;
167✔
855

856
    // No locality selected
857
    MemorySet(&session->commandLocality, 0, sizeof(session->commandLocality));
167✔
858

859
    // The cpHash size to zero
860
    session->u1.cpHash.b.size = 0;
167✔
861

862
    // No timeout
863
    session->timeout = 0;
167✔
864

865
    // Reset the pcrCounter
866
    session->pcrCounter = 0;
167✔
867

868
    // Reset the policy hash
869
    MemorySet(&session->u2.policyDigest.t.buffer, 0, session->u2.policyDigest.t.size);
167✔
870

871
    // Reset the session attributes
872
    MemorySet(&session->attributes, 0, sizeof(SESSION_ATTRIBUTES));
167✔
873

874
    // Restore the policy attributes
875
    session->attributes.isPolicy      = SET;
167✔
876
    session->attributes.isTrialPolicy = oldAttributes.isTrialPolicy;
167✔
877

878
    // Restore the bind attributes
879
    session->attributes.isDaBound      = oldAttributes.isDaBound;
167✔
880
    session->attributes.isLockoutBound = oldAttributes.isLockoutBound;
167✔
881
}
167✔
882

883
//*** SessionCapGetLoaded()
884
// This function returns a list of handles of loaded session, started
885
// from input 'handle'
886
//
887
// 'Handle' must be in valid loaded session handle range, but does not
888
// have to point to a loaded session.
889
//  Return Type: TPMI_YES_NO
890
//      YES         if there are more handles available
891
//      NO          all the available handles has been returned
892
TPMI_YES_NO
893
SessionCapGetLoaded(TPMI_SH_POLICY handle,     // IN: start handle
37✔
894
                    UINT32         count,      // IN: count of returned handles
895
                    TPML_HANDLE*   handleList  // OUT: list of handle
896
)
897
{
898
    TPMI_YES_NO more = NO;
37✔
899
    UINT32      i;
37✔
900

901
    pAssert(HandleGetType(handle) == TPM_HT_LOADED_SESSION);
37✔
902

903
    // Initialize output handle list
904
    handleList->count = 0;
37✔
905

906
    // The maximum count of handles we may return is MAX_CAP_HANDLES
907
    if(count > MAX_CAP_HANDLES)
37✔
908
        count = MAX_CAP_HANDLES;
909

910
    // Iterate session context ID slots to get loaded session handles
911
    for(i = handle & HR_HANDLE_MASK; i < MAX_ACTIVE_SESSIONS; i++)
497✔
912
    {
913
        // If session is active
914
        if(gr.contextArray[i] != 0)
460✔
915
        {
916
            // If session is loaded
917
            if(gr.contextArray[i] <= MAX_LOADED_SESSIONS)
×
918
            {
919
                if(handleList->count < count)
×
920
                {
921
                    SESSION* session;
×
922

923
                    // If we have not filled up the return list, add this
924
                    // session handle to it
925
                    // assume that this is going to be an HMAC session
926
                    handle  = i + HMAC_SESSION_FIRST;
×
927
                    session = SessionGet(handle);
×
928
                    if(session->attributes.isPolicy)
×
929
                        handle = i + POLICY_SESSION_FIRST;
×
930
                    handleList->handle[handleList->count] = handle;
×
931
                    handleList->count++;
×
932
                }
933
                else
934
                {
935
                    // If the return list is full but we still have loaded object
936
                    // available, report this and stop iterating
937
                    more = YES;
938
                    break;
939
                }
940
            }
941
        }
942
    }
943

944
    return more;
37✔
945
}
946

947
//*** SessionCapGetOneLoaded()
948
// This function returns whether a session handle exists and is loaded.
949
BOOL SessionCapGetOneLoaded(TPMI_SH_POLICY handle)  // IN: handle
×
950
{
951
    pAssert(HandleGetType(handle) == TPM_HT_LOADED_SESSION);
×
952

953
    if((handle & HR_HANDLE_MASK) < MAX_ACTIVE_SESSIONS
×
954
       && gr.contextArray[(handle & HR_HANDLE_MASK)])
×
955
    {
956
        return TRUE;
×
957
    }
958

959
    return FALSE;
960
}
961

962
//*** SessionCapGetSaved()
963
// This function returns a list of handles for saved session, starting at
964
// 'handle'.
965
//
966
// 'Handle' must be in a valid handle range, but does not have to point to a
967
// saved session
968
//
969
//  Return Type: TPMI_YES_NO
970
//      YES         if there are more handles available
971
//      NO          all the available handles has been returned
972
TPMI_YES_NO
973
SessionCapGetSaved(TPMI_SH_HMAC handle,     // IN: start handle
33✔
974
                   UINT32       count,      // IN: count of returned handles
975
                   TPML_HANDLE* handleList  // OUT: list of handle
976
)
977
{
978
    TPMI_YES_NO more = NO;
33✔
979
    UINT32      i;
33✔
980

981
    pAssert(HandleGetType(handle) == TPM_HT_SAVED_SESSION);
33✔
982

983
    // Initialize output handle list
984
    handleList->count = 0;
33✔
985

986
    // The maximum count of handles we may return is MAX_CAP_HANDLES
987
    if(count > MAX_CAP_HANDLES)
33✔
988
        count = MAX_CAP_HANDLES;
989

990
    // Iterate session context ID slots to get loaded session handles
991
    for(i = handle & HR_HANDLE_MASK; i < MAX_ACTIVE_SESSIONS; i++)
545✔
992
    {
993
        // If session is active
994
        if(gr.contextArray[i] != 0)
512✔
995
        {
996
            // If session is saved
997
            if(gr.contextArray[i] > MAX_LOADED_SESSIONS)
×
998
            {
999
                if(handleList->count < count)
×
1000
                {
1001
                    // If we have not filled up the return list, add this
1002
                    // session handle to it
1003
                    handleList->handle[handleList->count] = i + HMAC_SESSION_FIRST;
×
1004
                    handleList->count++;
×
1005
                }
1006
                else
1007
                {
1008
                    // If the return list is full but we still have loaded object
1009
                    // available, report this and stop iterating
1010
                    more = YES;
1011
                    break;
1012
                }
1013
            }
1014
        }
1015
    }
1016

1017
    return more;
33✔
1018
}
1019

1020
//*** SessionCapGetOneSaved()
1021
// This function returns whether a session handle exists and is saved.
1022
BOOL SessionCapGetOneSaved(TPMI_SH_HMAC handle)  // IN: handle
×
1023
{
1024
    pAssert(HandleGetType(handle) == TPM_HT_SAVED_SESSION);
×
1025

1026
    if((handle & HR_HANDLE_MASK) < MAX_ACTIVE_SESSIONS
×
1027
       && gr.contextArray[(handle & HR_HANDLE_MASK)])
×
1028
    {
1029
        return TRUE;
×
1030
    }
1031

1032
    return FALSE;
1033
}
1034

1035
//*** SessionCapGetLoadedNumber()
1036
// This function return the number of authorization sessions currently
1037
// loaded into TPM RAM.
1038
UINT32
1039
SessionCapGetLoadedNumber(void)
1✔
1040
{
1041
    return MAX_LOADED_SESSIONS - s_freeSessionSlots;
1✔
1042
}
1043

1044
//*** SessionCapGetLoadedAvail()
1045
// This function returns the number of additional authorization sessions, of
1046
// any type, that could be loaded into TPM RAM.
1047
// NOTE: In other implementations, this number may just be an estimate. The only
1048
//       requirement for the estimate is, if it is one or more, then at least one
1049
//       session must be loadable.
1050
UINT32
1051
SessionCapGetLoadedAvail(void)
1✔
1052
{
1053
    return s_freeSessionSlots;
1✔
1054
}
1055

1056
//*** SessionCapGetActiveNumber()
1057
// This function returns the number of active authorization sessions currently
1058
// being tracked by the TPM.
1059
UINT32
1060
SessionCapGetActiveNumber(void)
1✔
1061
{
1062
    UINT32 i;
1✔
1063
    UINT32 num = 0;
1✔
1064

1065
    // Iterate the context array to find the number of non-zero slots
1066
    for(i = 0; i < MAX_ACTIVE_SESSIONS; i++)
65✔
1067
    {
1068
        if(gr.contextArray[i] != 0)
64✔
1069
            num++;
×
1070
    }
1071

1072
    return num;
1✔
1073
}
1074

1075
//*** SessionCapGetActiveAvail()
1076
// This function returns the number of additional authorization sessions, of any
1077
// type, that could be created. This not the number of slots for sessions, but
1078
// the number of additional sessions that the TPM is capable of tracking.
1079
UINT32
1080
SessionCapGetActiveAvail(void)
1✔
1081
{
1082
    UINT32 i;
1✔
1083
    UINT32 num = 0;
1✔
1084

1085
    // Iterate the context array to find the number of zero slots
1086
    for(i = 0; i < MAX_ACTIVE_SESSIONS; i++)
65✔
1087
    {
1088
        if(gr.contextArray[i] == 0)
64✔
1089
            num++;
64✔
1090
    }
1091

1092
    return num;
1✔
1093
}
1094

1095
//*** IsCpHashUnionOccupied()
1096
// This function indicates whether the session attributes indicate that one of
1097
// the members of the union containing `cpHash` are set.
1098
BOOL IsCpHashUnionOccupied(SESSION_ATTRIBUTES attrs)
8✔
1099
{
1100
    return attrs.isBound || attrs.isCpHashDefined || attrs.isNameHashDefined
8✔
1101
        || attrs.isParametersHashDefined || attrs.isTemplateHashDefined;
8✔
1102
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc