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

drakenclimber / libseccomp / 6279355589

21 Sep 2023 04:53PM UTC coverage: 89.542% (-0.3%) from 89.89%
6279355589

push

github

pcmoore
api: add transaction support to the libseccomp API

While libseccomp has internally has transaction support for some time
now, it hasn't been accessible to callers through the libseccomp API.
This patch adds a transaction API as well as supporting documentation
and a new unit regression test.

  int seccomp_transaction_start(const scmp_filter_ctx ctx)
  int seccomp_transaction_commit(const scmp_filter_ctx ctx)
  void seccomp_transaction_reject(const scmp_filter_ctx ctx)

Signed-off-by: Paul Moore <paul@paul-moore.com>

30 of 30 new or added lines in 2 files covered. (100.0%)

2697 of 3012 relevant lines covered (89.54%)

299730.21 hits per line

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

82.16
/src/system.c
1
/**
2
 * Seccomp System Interfaces
3
 *
4
 * Copyright (c) 2014 Red Hat <pmoore@redhat.com>
5
 * Author: Paul Moore <paul@paul-moore.com>
6
 */
7

8
/*
9
 * This library is free software; you can redistribute it and/or modify it
10
 * under the terms of version 2.1 of the GNU Lesser General Public License as
11
 * published by the Free Software Foundation.
12
 *
13
 * This library is distributed in the hope that it will be useful, but WITHOUT
14
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
16
 * for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with this library; if not, see <http://www.gnu.org/licenses>.
20
 */
21

22
#include <stdlib.h>
23
#include <errno.h>
24
#include <sys/prctl.h>
25

26
#define _GNU_SOURCE
27
#include <unistd.h>
28

29
#include "system.h"
30

31
#include <seccomp.h>
32

33
#include "arch.h"
34
#include "db.h"
35
#include "gen_bpf.h"
36
#include "helper.h"
37

38
/* NOTE: the seccomp syscall allowlist is currently disabled for testing
39
 *       purposes, but unless we can verify all of the supported ABIs before
40
 *       our next release we may have to enable the allowlist */
41
#define SYSCALL_ALLOWLIST_ENABLE        0
42

43
/* task global state */
44
struct task_state {
45
        /* seccomp(2) syscall */
46
        int nr_seccomp;
47

48
        /* userspace notification fd */
49
        int notify_fd;
50

51
        /* runtime support flags */
52
        int sup_syscall;
53
        int sup_flag_tsync;
54
        int sup_flag_log;
55
        int sup_action_log;
56
        int sup_kill_process;
57
        int sup_flag_spec_allow;
58
        int sup_flag_new_listener;
59
        int sup_user_notif;
60
        int sup_flag_tsync_esrch;
61
        int sup_flag_wait_kill;
62
};
63
static struct task_state state = {
64
        .nr_seccomp = -1,
65

66
        .notify_fd = -1,
67

68
        .sup_syscall = -1,
69
        .sup_flag_tsync = -1,
70
        .sup_flag_log = -1,
71
        .sup_action_log = -1,
72
        .sup_kill_process = -1,
73
        .sup_flag_spec_allow = -1,
74
        .sup_flag_new_listener = -1,
75
        .sup_user_notif = -1,
76
        .sup_flag_tsync_esrch = -1,
77
        .sup_flag_wait_kill = -1,
78
};
79

80
/**
81
 * Reset the task state
82
 *
83
 * This function fully resets the library's global "system task state".
84
 *
85
 */
86
void sys_reset_state(void)
1✔
87
{
88
        state.nr_seccomp = -1;
1✔
89

90
        if (state.notify_fd > 0)
1✔
91
                close(state.notify_fd);
×
92
        state.notify_fd = -1;
1✔
93

94
        state.sup_syscall = -1;
1✔
95
        state.sup_flag_tsync = -1;
1✔
96
        state.sup_flag_log = -1;
1✔
97
        state.sup_action_log = -1;
1✔
98
        state.sup_kill_process = -1;
1✔
99
        state.sup_flag_spec_allow = -1;
1✔
100
        state.sup_flag_new_listener = -1;
1✔
101
        state.sup_user_notif = -1;
1✔
102
        state.sup_flag_tsync_esrch = -1;
1✔
103
}
1✔
104

105
/**
106
 * Check to see if the seccomp() syscall is supported
107
 *
108
 * This function attempts to see if the system supports the seccomp() syscall.
109
 * Unfortunately, there are a few reasons why this check may fail, including
110
 * a previously loaded seccomp filter, so it is hard to say for certain.
111
 * Return one if the syscall is supported, zero otherwise.
112
 *
113
 */
114
int sys_chk_seccomp_syscall(void)
70,852✔
115
{
116
        int rc;
70,852✔
117
        int nr_seccomp;
70,852✔
118

119
        /* NOTE: it is reasonably safe to assume that we should be able to call
120
         *       seccomp() when the caller first starts, but we can't rely on
121
         *       it later so we need to cache our findings for use later */
122
        if (state.sup_syscall >= 0)
70,852✔
123
                return state.sup_syscall;
124

125
#if SYSCALL_ALLOWLIST_ENABLE
126
        /* architecture allowlist */
127
        switch (arch_def_native->token) {
128
        case SCMP_ARCH_X86_64:
129
        case SCMP_ARCH_ARM:
130
        case SCMP_ARCH_AARCH64:
131
        case SCMP_ARCH_LOONGARCH64:
132
        case SCMP_ARCH_PPC64:
133
        case SCMP_ARCH_PPC64LE:
134
        case SCMP_ARCH_S390:
135
        case SCMP_ARCH_S390X:
136
        case SCMP_ARCH_RISCV64:
137
                break;
138
        default:
139
                goto unsupported;
140
        }
141
#endif
142

143
        nr_seccomp = arch_syscall_resolve_name(arch_def_native, "seccomp");
7,086✔
144
        if (nr_seccomp < 0)
7,086✔
145
                goto unsupported;
×
146

147
        /* this is an invalid call because the second argument is non-zero, but
148
         * depending on the errno value of ENOSYS or EINVAL we can guess if the
149
         * seccomp() syscall is supported or not */
150
        rc = syscall(nr_seccomp, SECCOMP_SET_MODE_STRICT, 1, NULL);
7,086✔
151
        if (rc < 0 && errno == EINVAL)
7,086✔
152
                goto supported;
7,086✔
153

154
unsupported:
×
155
        state.sup_syscall = 0;
×
156
        return 0;
×
157
supported:
7,086✔
158
        state.nr_seccomp = nr_seccomp;
7,086✔
159
        state.sup_syscall = 1;
7,086✔
160
        return 1;
7,086✔
161
}
162

163
/**
164
 * Force the seccomp() syscall support setting
165
 * @param enable the intended support state
166
 *
167
 * This function overrides the current seccomp() syscall support setting; this
168
 * is very much a "use at your own risk" function.
169
 *
170
 */
171
void sys_set_seccomp_syscall(bool enable)
723✔
172
{
173
        state.sup_syscall = (enable ? 1 : 0);
723✔
174
}
723✔
175

176
/**
177
 * Check to see if a seccomp action is supported
178
 * @param action the seccomp action
179
 *
180
 * This function checks to see if a seccomp action is supported by the system.
181
 * Return one if the action is supported, zero otherwise.
182
 *
183
 */
184
int sys_chk_seccomp_action(uint32_t action)
82,774✔
185
{
186
        if (action == SCMP_ACT_KILL_PROCESS) {
82,774✔
187
                if (state.sup_kill_process < 0) {
7,440✔
188
                        if (sys_chk_seccomp_syscall() == 1 &&
14,170✔
189
                            syscall(state.nr_seccomp,
7,085✔
190
                                    SECCOMP_GET_ACTION_AVAIL, 0, &action) == 0)
191
                                state.sup_kill_process = 1;
7,085✔
192
                        else
193
                                state.sup_kill_process = 0;
×
194
                }
195

196
                return state.sup_kill_process;
7,440✔
197
        } else if (action == SCMP_ACT_KILL_THREAD) {
75,334✔
198
                return 1;
199
        } else if (action == SCMP_ACT_TRAP) {
66,273✔
200
                return 1;
201
        } else if ((action == SCMP_ACT_ERRNO(action & 0x0000ffff)) &&
65,250✔
202
                   ((action & 0x0000ffff) < MAX_ERRNO)) {
203
                return 1;
204
        } else if (action == SCMP_ACT_TRACE(action & 0x0000ffff)) {
58,665✔
205
                return 1;
206
        } else if (action == SCMP_ACT_LOG) {
58,316✔
207
                if (state.sup_action_log < 0) {
7,785✔
208
                        if (sys_chk_seccomp_syscall() == 1 &&
14,170✔
209
                            syscall(state.nr_seccomp,
7,085✔
210
                                    SECCOMP_GET_ACTION_AVAIL, 0, &action) == 0)
211
                                state.sup_action_log = 1;
7,085✔
212
                        else
213
                                state.sup_action_log = 0;
×
214
                }
215

216
                return state.sup_action_log;
7,785✔
217
        } else if (action == SCMP_ACT_ALLOW) {
50,531✔
218
                return 1;
219
        } else if (action == SCMP_ACT_NOTIFY) {
7,089✔
220
                if (state.sup_user_notif < 0) {
7,085✔
221
                        struct seccomp_notif_sizes sizes;
7,085✔
222
                        if (sys_chk_seccomp_syscall() == 1 &&
14,170✔
223
                            syscall(state.nr_seccomp,
7,085✔
224
                                    SECCOMP_GET_NOTIF_SIZES, 0, &sizes) == 0)
225
                                state.sup_user_notif = 1;
7,085✔
226
                        else
227
                                state.sup_user_notif = 0;
×
228
                }
229

230
                return state.sup_user_notif;
7,085✔
231
        }
232

233
        return 0;
234
}
235

236
/**
237
 * Force a seccomp action support setting
238
 * @param action the seccomp action
239
 * @param enable the intended support state
240
 *
241
 * This function overrides the current seccomp action support setting; this
242
 * is very much a "use at your own risk" function.
243
 */
244
void sys_set_seccomp_action(uint32_t action, bool enable)
2,169✔
245
{
246
        switch (action) {
2,169✔
247
        case SCMP_ACT_LOG:
723✔
248
                state.sup_action_log = (enable ? 1 : 0);
723✔
249
                break;
723✔
250
        case SCMP_ACT_KILL_PROCESS:
723✔
251
                state.sup_kill_process = (enable ? 1 : 0);
723✔
252
                break;
723✔
253
        case SCMP_ACT_NOTIFY:
723✔
254
                state.sup_user_notif = (enable ? 1 : 0);
723✔
255
                break;
723✔
256
        }
257
}
2,169✔
258

259
/**
260
 * Check to see if a seccomp() flag is supported by the kernel
261
 * @param flag the seccomp() flag
262
 *
263
 * This function checks to see if a seccomp() flag is supported by the kernel.
264
 * Return one if the flag is supported, zero otherwise.
265
 *
266
 */
267
static int _sys_chk_flag_kernel(int flag)
42,511✔
268
{
269
        /* this is an invalid seccomp(2) call because the last argument
270
         * is NULL, but depending on the errno value of EFAULT we can
271
         * guess if the filter flag is supported or not */
272
        if (sys_chk_seccomp_syscall() == 1 &&
85,022✔
273
            syscall(state.nr_seccomp,
42,511✔
274
                    SECCOMP_SET_MODE_FILTER, flag, NULL) == -1 &&
42,511✔
275
            errno == EFAULT)
42,511✔
276
                return 1;
35,426✔
277

278
        return 0;
279
}
280

281
/**
282
 * Check to see if a seccomp() flag is supported
283
 * @param flag the seccomp() flag
284
 *
285
 * This function checks to see if a seccomp() flag is supported by the system.
286
 * Return one if the syscall is supported, zero if unsupported, negative values
287
 * on error.
288
 *
289
 */
290
int sys_chk_seccomp_flag(int flag)
95,835✔
291
{
292
        switch (flag) {
95,835✔
293
        case SECCOMP_FILTER_FLAG_TSYNC:
7,087✔
294
                if (state.sup_flag_tsync < 0)
7,087✔
295
                        state.sup_flag_tsync = _sys_chk_flag_kernel(flag);
7,085✔
296
                return state.sup_flag_tsync;
7,087✔
297
        case SECCOMP_FILTER_FLAG_LOG:
7,087✔
298
                if (state.sup_flag_log < 0)
7,087✔
299
                        state.sup_flag_log = _sys_chk_flag_kernel(flag);
7,085✔
300
                return state.sup_flag_log;
7,087✔
301
        case SECCOMP_FILTER_FLAG_SPEC_ALLOW:
7,087✔
302
                if (state.sup_flag_spec_allow < 0)
7,087✔
303
                        state.sup_flag_spec_allow = _sys_chk_flag_kernel(flag);
7,085✔
304
                return state.sup_flag_spec_allow;
7,087✔
305
        case SECCOMP_FILTER_FLAG_NEW_LISTENER:
7,085✔
306
                if (state.sup_flag_new_listener < 0)
7,085✔
307
                        state.sup_flag_new_listener = _sys_chk_flag_kernel(flag);
7,085✔
308
                return state.sup_flag_new_listener;
7,085✔
309
        case SECCOMP_FILTER_FLAG_TSYNC_ESRCH:
60,404✔
310
                if (state.sup_flag_tsync_esrch < 0)
60,404✔
311
                        state.sup_flag_tsync_esrch = _sys_chk_flag_kernel(flag);
7,086✔
312
                return state.sup_flag_tsync_esrch;
60,404✔
313
        case SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV:
7,085✔
314
                if (state.sup_flag_wait_kill < 0)
7,085✔
315
                        state.sup_flag_wait_kill = _sys_chk_flag_kernel(flag);
7,085✔
316
                return state.sup_flag_wait_kill;
7,085✔
317
        }
318

319
        return -EOPNOTSUPP;
320
}
321

322
/**
323
 * Force a seccomp() syscall flag support setting
324
 * @param flag the seccomp() flag
325
 * @param enable the intended support state
326
 *
327
 * This function overrides the current seccomp() syscall support setting for a
328
 * given flag; this is very much a "use at your own risk" function.
329
 *
330
 */
331
void sys_set_seccomp_flag(int flag, bool enable)
4,338✔
332
{
333
        switch (flag) {
4,338✔
334
        case SECCOMP_FILTER_FLAG_TSYNC:
723✔
335
                state.sup_flag_tsync = (enable ? 1 : 0);
723✔
336
                break;
723✔
337
        case SECCOMP_FILTER_FLAG_LOG:
723✔
338
                state.sup_flag_log = (enable ? 1 : 0);
723✔
339
                break;
723✔
340
        case SECCOMP_FILTER_FLAG_SPEC_ALLOW:
723✔
341
                state.sup_flag_spec_allow = (enable ? 1 : 0);
723✔
342
                break;
723✔
343
        case SECCOMP_FILTER_FLAG_NEW_LISTENER:
723✔
344
                state.sup_flag_new_listener = (enable ? 1 : 0);
723✔
345
                break;
723✔
346
        case SECCOMP_FILTER_FLAG_TSYNC_ESRCH:
723✔
347
                state.sup_flag_tsync_esrch = (enable ? 1 : 0);
723✔
348
                break;
723✔
349
        case SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV:
723✔
350
                state.sup_flag_wait_kill = (enable ? 1 : 0);
723✔
351
                break;
723✔
352
        }
353
}
4,338✔
354

355
/**
356
 * Loads the filter into the kernel
357
 * @param col the filter collection
358
 * @param rawrc pass the raw return code if true
359
 *
360
 * This function loads the given seccomp filter context into the kernel.  If
361
 * the filter was loaded correctly, the kernel will be enforcing the filter
362
 * when this function returns.  Returns zero on success, negative values on
363
 * error.
364
 *
365
 */
366
int sys_filter_load(struct db_filter_col *col, bool rawrc)
1✔
367
{
368
        int rc;
1✔
369
        bool tsync_notify;
1✔
370
        bool listener_req;
1✔
371
        struct bpf_program *prgm = NULL;
1✔
372

373
        rc = db_col_precompute(col);
1✔
374
        if (rc < 0)
1✔
375
                return rc;
376
        prgm = col->prgm_bpf;
1✔
377

378
        /* attempt to set NO_NEW_PRIVS */
379
        if (col->attr.nnp_enable) {
1✔
380
                rc = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1✔
381
                if (rc < 0)
1✔
382
                        goto filter_load_out;
×
383
        }
384

385
        tsync_notify = state.sup_flag_tsync_esrch > 0 && state.notify_fd == -1;
1✔
386
        listener_req = state.sup_user_notif > 0 && \
3✔
387
                       col->notify_used && state.notify_fd == -1;
1✔
388

389
        /* load the filter into the kernel */
390
        if (sys_chk_seccomp_syscall() == 1) {
1✔
391
                int flgs = 0;
1✔
392
                if (tsync_notify) {
1✔
393
                        if (col->attr.tsync_enable)
1✔
394
                                flgs |= SECCOMP_FILTER_FLAG_TSYNC | \
1✔
395
                                        SECCOMP_FILTER_FLAG_TSYNC_ESRCH;
396
                        if (listener_req)
1✔
397
                                flgs |= SECCOMP_FILTER_FLAG_NEW_LISTENER;
×
398
                } else if (col->attr.tsync_enable) {
×
399
                        if (listener_req) {
×
400
                                /* NOTE: we _should_ catch this in db.c */
401
                                rc = -EFAULT;
×
402
                                goto filter_load_out;
×
403
                        }
404
                        flgs |= SECCOMP_FILTER_FLAG_TSYNC;
405
                } else if (listener_req)
×
406
                        flgs |= SECCOMP_FILTER_FLAG_NEW_LISTENER;
×
407
                if ((flgs & SECCOMP_FILTER_FLAG_NEW_LISTENER) &&
1✔
408
                    col->attr.wait_killable_recv)
×
409
                        flgs |= SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV;
×
410
                if (col->attr.log_enable)
1✔
411
                        flgs |= SECCOMP_FILTER_FLAG_LOG;
1✔
412
                if (col->attr.spec_allow)
1✔
413
                        flgs |= SECCOMP_FILTER_FLAG_SPEC_ALLOW;
1✔
414
                rc = syscall(state.nr_seccomp,
1✔
415
                             SECCOMP_SET_MODE_FILTER, flgs, prgm);
416
                if (tsync_notify && rc > 0) {
1✔
417
                        /* return 0 on NEW_LISTENER success, but save the fd */
418
                        state.notify_fd = rc;
×
419
                        rc = 0;
×
420
                } else if (rc > 0 && col->attr.tsync_enable) {
1✔
421
                        /* always return -ESRCH if we fail to sync threads */
422
                        errno = ESRCH;
×
423
                        rc = -errno;
×
424
                } else if (rc > 0 && state.sup_user_notif > 0) {
1✔
425
                        /* return 0 on NEW_LISTENER success, but save the fd */
426
                        state.notify_fd = rc;
×
427
                        rc = 0;
×
428
                }
429
        } else
430
                rc = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prgm);
×
431

432
filter_load_out:
1✔
433
        /* cleanup and return */
434
        if (rc == -ESRCH)
1✔
435
                return -ESRCH;
436
        if (rc < 0)
1✔
437
                return (rawrc ? -errno : -ECANCELED);
×
438
        return rc;
439
}
440

441
/**
442
 * Return the userspace notification fd
443
 *
444
 * This function returns the userspace notification fd from
445
 * SECCOMP_FILTER_FLAG_NEW_LISTENER.  If the notification fd has not yet been
446
 * set, or an error has occurred, -1 is returned.
447
 *
448
 */
449
int sys_notify_fd(void)
1✔
450
{
451
        return state.notify_fd;
1✔
452
}
453

454
/**
455
 * Allocate a pair of notification request/response structures
456
 * @param req the request location
457
 * @param resp the response location
458
 *
459
 * This function allocates a pair of request/response structure by computing
460
 * the correct sized based on the currently running kernel. It returns zero on
461
 * success, and negative values on failure.
462
 *
463
 */
464
int sys_notify_alloc(struct seccomp_notif **req,
3✔
465
                     struct seccomp_notif_resp **resp)
466
{
467
        int rc;
3✔
468
        static struct seccomp_notif_sizes sizes = { 0, 0, 0 };
3✔
469

470
        if (state.sup_syscall <= 0)
3✔
471
                return -EOPNOTSUPP;
472

473
        if (sizes.seccomp_notif == 0 && sizes.seccomp_notif_resp == 0) {
3✔
474
                rc = syscall(__NR_seccomp, SECCOMP_GET_NOTIF_SIZES, 0, &sizes);
1✔
475
                if (rc < 0)
1✔
476
                        return -ECANCELED;
477
        }
478
        if (sizes.seccomp_notif == 0 || sizes.seccomp_notif_resp == 0)
3✔
479
                return -EFAULT;
480

481
        if (req) {
3✔
482
                *req = zmalloc(sizes.seccomp_notif);
1✔
483
                if (!*req)
1✔
484
                        return -ENOMEM;
485
        }
486

487
        if (resp) {
3✔
488
                *resp = zmalloc(sizes.seccomp_notif_resp);
1✔
489
                if (!*resp) {
1✔
490
                        if (req)
×
491
                                free(*req);
×
492
                        return -ENOMEM;
×
493
                }
494
        }
495

496
        return 0;
497
}
498

499
/**
500
 * Receive a notification from a seccomp notification fd
501
 * @param fd the notification fd
502
 * @param req the request buffer to save into
503
 *
504
 * Blocks waiting for a notification on this fd. This function is thread safe
505
 * (synchronization is performed in the kernel). Returns zero on success,
506
 * negative values on error.
507
 *
508
 */
509
int sys_notify_receive(int fd, struct seccomp_notif *req)
1✔
510
{
511
        if (state.sup_user_notif <= 0)
1✔
512
                return -EOPNOTSUPP;
513

514
        if (ioctl(fd, SECCOMP_IOCTL_NOTIF_RECV, req) < 0)
×
515
                return -ECANCELED;
×
516

517
        return 0;
518
}
519

520
/**
521
 * Send a notification response to a seccomp notification fd
522
 * @param fd the notification fd
523
 * @param resp the response buffer to use
524
 *
525
 * Sends a notification response on this fd. This function is thread safe
526
 * (synchronization is performed in the kernel). Returns zero on success,
527
 * negative values on error.
528
 *
529
 */
530
int sys_notify_respond(int fd, struct seccomp_notif_resp *resp)
1✔
531
{
532
        if (state.sup_user_notif <= 0)
1✔
533
                return -EOPNOTSUPP;
534

535
        if (ioctl(fd, SECCOMP_IOCTL_NOTIF_SEND, resp) < 0)
×
536
                return -ECANCELED;
×
537
        return 0;
538
}
539

540
/**
541
 * Check if a notification id is still valid
542
 * @param fd the notification fd
543
 * @param id the id to test
544
 *
545
 * Checks to see if a notification id is still valid. Returns 0 on success, and
546
 * negative values on failure.
547
 *
548
 */
549
int sys_notify_id_valid(int fd, uint64_t id)
1✔
550
{
551
        int rc;
1✔
552
        if (state.sup_user_notif <= 0)
1✔
553
                return -EOPNOTSUPP;
554

555
        rc = ioctl(fd, SECCOMP_IOCTL_NOTIF_ID_VALID, &id);
×
556
        if (rc < 0 && errno == EINVAL)
×
557
                /* It is possible that libseccomp was built against newer kernel
558
                 * headers than the kernel it is running on. If so, the older
559
                 * runtime kernel may not support the "fixed"
560
                 * SECCOMP_IOCTL_NOTIF_ID_VALID ioctl number which was introduced in
561
                 * kernel commit 47e33c05f9f0 ("seccomp: Fix ioctl number for
562
                 * SECCOMP_IOCTL_NOTIF_ID_VALID"). Try the old value. */
563
                rc = ioctl(fd, SECCOMP_IOCTL_NOTIF_ID_VALID_WRONG_DIR, &id);
×
564
        if (rc < 0)
×
565
                return -ENOENT;
×
566
        return 0;
567
}
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