diff --git a/doc/man/man3/seccomp_transaction_start.3 b/doc/man/man3/seccomp_transaction_start.3 new file mode 100644 index 00000000..93605047 --- /dev/null +++ b/doc/man/man3/seccomp_transaction_start.3 @@ -0,0 +1,134 @@ +.TH "seccomp_transaction_start" 3 "21 September 2023" "paul@paul-moore.com" "libseccomp Documentation" +.\" ////////////////////////////////////////////////////////////////////////// +.SH NAME +.\" ////////////////////////////////////////////////////////////////////////// +seccomp_transaction_start, seccomp_transaction_commit, seccomp_transaction_reject \- Manage seccomp filter transactions +.\" ////////////////////////////////////////////////////////////////////////// +.SH SYNOPSIS +.\" ////////////////////////////////////////////////////////////////////////// +.nf +.B #include +.sp +.B typedef void * scmp_filter_ctx; +.sp +.BI "int seccomp_transaction_start(scmp_filter_ctx " ctx "); +.BI "int seccomp_transaction_commit(scmp_filter_ctx " ctx "); +.BI "void seccomp_transaction_reject(scmp_filter_ctx " ctx "); +.sp +Link with \fI\-lseccomp\fP. +.fi +.\" ////////////////////////////////////////////////////////////////////////// +.SH DESCRIPTION +.\" ////////////////////////////////////////////////////////////////////////// +.P +The +.BR seccomp_transaction_start () +function starts a new seccomp filter +transaction that the caller can use to perform any number of filter +modifications which can then be committed to the filter using +.BR seccomp_transaction_commit () +or rejected using +.BR seccomp_transaction_reject (). +It is important to note that transactions only affect the seccomp filter state +while it is being managed by libseccomp; seccomp filters which have been loaded +into the kernel can not be modified, only new seccomp filters can be added on +top of the existing loaded filter stack. +.P +Finishing, or committing, a transaction is optional, although it is encouraged. +At any point in time, regardless of the transaction state, the seccomp filter +is determined by all of the libseccomp operations performed on the filter up to +that point. Committing a transaction simply flushes the transaction rollback +marker of the current transaction making the filter changes permanent; +rejecting a transaction rolls the filter state back to immediately before the +transaction was started. +.P +Transactions can be nested arbitrarily deep with the +.BR seccomp_transaction_commit () +and +.BR seccomp_transaction_reject () +functions always operating on the deepest, or more recently started transaction. +A nested set of filter modifications, even if committed, is still subject to +rejection by shallower, or older transactions that have yet to be committed or +rejected. +.\" ////////////////////////////////////////////////////////////////////////// +.SH RETURN VALUE +.\" ////////////////////////////////////////////////////////////////////////// +The +.BR seccomp_transaction_start () +and +.BR seccomp_transaction_commit () +functions return zero on success or one of the following error codes on +failure: +.TP +.B -ENOMEM +The library was unable to allocate enough memory. +.\" ////////////////////////////////////////////////////////////////////////// +.SH EXAMPLES +.\" ////////////////////////////////////////////////////////////////////////// +.nf +#include + +int libseccomp_generate(scmp_filter_ctx *ctx) +{ + int rc; + + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); + if (rc) + return rc; + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); + if (rc) + return rc; + rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); + if (rc) + return rc; + + return 0; +} + +int main(int argc, char *argv[]) +{ + int rc = \-1; + scmp_filter_ctx ctx; + + ctx = seccomp_init(SCMP_ACT_KILL); + if (ctx == NULL) + goto out; + + rc = seccomp_transaction_start(ctx) + if (rc) + goto out; + rc = libseccomp_generate(ctx); + if (rc == 0) { + rc = seccomp_transaction_commit(ctx); + if (rc) + goto out; + } else + seccomp_transaction_reject(ctx); + + /* ... */ + +out: + seccomp_release(ctx); + return \-rc; +} +.fi +.\" ////////////////////////////////////////////////////////////////////////// +.SH NOTES +.\" ////////////////////////////////////////////////////////////////////////// +.P +While the seccomp filter can be generated independent of the kernel, kernel +support is required to load and enforce the seccomp filter generated by +libseccomp. +.P +The libseccomp project site, with more information and the source code +repository, can be found at https://github.com/seccomp/libseccomp. This tool, +as well as the libseccomp library, is currently under development, please +report any bugs at the project site or directly to the author. +.\" ////////////////////////////////////////////////////////////////////////// +.SH AUTHOR +.\" ////////////////////////////////////////////////////////////////////////// +Paul Moore +.\" ////////////////////////////////////////////////////////////////////////// +.SH SEE ALSO +.\" ////////////////////////////////////////////////////////////////////////// +.BR seccomp_init (3), diff --git a/include/seccomp.h.in b/include/seccomp.h.in index e2d7c0e9..9f9eeb1f 100644 --- a/include/seccomp.h.in +++ b/include/seccomp.h.in @@ -849,6 +849,35 @@ int seccomp_export_bpf(const scmp_filter_ctx ctx, int fd); */ int seccomp_export_bpf_mem(const scmp_filter_ctx ctx, void *buf, size_t *len); +/** + * Start a filter transaction + * @param ctx the filter context + * + * This function starts a filter transaction for modifying the seccomp filter. + * Returns zero on success, negative values on failure. + * + */ +int seccomp_transaction_start(const scmp_filter_ctx ctx); + +/** + * Reject the current filter transaction + * @param ctx the filter context + * + * This function rejects the current seccomp filter transaction. + * + */ +void seccomp_transaction_reject(const scmp_filter_ctx ctx); + +/** + * Commit the current filter transaction + * @param ctx the filter context + * + * This function commits the current seccomp filter transaction. Returns zero + * on success, negative values on failure. + * + */ +int seccomp_transaction_commit(const scmp_filter_ctx ctx); + /** * Precompute the seccomp filter for future use * @param ctx the filter context diff --git a/src/api.c b/src/api.c index b85e3b87..adccef32 100644 --- a/src/api.c +++ b/src/api.c @@ -793,6 +793,45 @@ API int seccomp_export_bpf_mem(const scmp_filter_ctx ctx, void *buf, return rc; } +/* NOTE - function header comment in include/seccomp.h */ +API int seccomp_transaction_start(const scmp_filter_ctx ctx) +{ + int rc; + struct db_filter_col *col; + + if (_ctx_valid(ctx)) + return _rc_filter(-EINVAL); + col = (struct db_filter_col *)ctx; + + rc = db_col_transaction_start(col, true); + return _rc_filter(rc); +} + +/* NOTE - function header comment in include/seccomp.h */ +API void seccomp_transaction_reject(const scmp_filter_ctx ctx) +{ + struct db_filter_col *col; + + if (_ctx_valid(ctx)) + return; + col = (struct db_filter_col *)ctx; + + db_col_transaction_abort(col, true); +} + +/* NOTE - function header comment in include/seccomp.h */ +API int seccomp_transaction_commit(const scmp_filter_ctx ctx) +{ + struct db_filter_col *col; + + if (_ctx_valid(ctx)) + return _rc_filter(-EINVAL); + col = (struct db_filter_col *)ctx; + + db_col_transaction_commit(col, true); + return _rc_filter(0); +} + /* NOTE - function header comment in include/seccomp.h */ API int seccomp_precompute(const scmp_filter_ctx ctx) { diff --git a/src/db.c b/src/db.c index ddc01c3c..15b7d753 100644 --- a/src/db.c +++ b/src/db.c @@ -2369,7 +2369,7 @@ int db_col_rule_add(struct db_filter_col *col, } /* create a checkpoint */ - rc = db_col_transaction_start(col); + rc = db_col_transaction_start(col, false); if (rc != 0) goto add_return; @@ -2396,9 +2396,9 @@ int db_col_rule_add(struct db_filter_col *col, /* commit the transaction or abort */ if (rc == 0) - db_col_transaction_commit(col); + db_col_transaction_commit(col, false); else - db_col_transaction_abort(col); + db_col_transaction_abort(col, false); add_return: /* update the misc state */ @@ -2415,12 +2415,13 @@ int db_col_rule_add(struct db_filter_col *col, /** * Start a new seccomp filter transaction * @param col the filter collection + * @param user true if initiated by a user * * This function starts a new seccomp filter transaction for the given filter * collection. Returns zero on success, negative values on failure. * */ -int db_col_transaction_start(struct db_filter_col *col) +int db_col_transaction_start(struct db_filter_col *col, bool user) { int rc; unsigned int iter; @@ -2439,6 +2440,7 @@ int db_col_transaction_start(struct db_filter_col *col) * transaction is current/correct */ col->snapshots->shadow = false; + col->snapshots->user = user; return 0; } @@ -2486,6 +2488,8 @@ int db_col_transaction_start(struct db_filter_col *col) } while (rule_o != filter_o->rules); } + snap->user = user; + /* add the snapshot to the list */ snap->next = col->snapshots; col->snapshots = snap; @@ -2502,23 +2506,35 @@ int db_col_transaction_start(struct db_filter_col *col) /** * Abort the top most seccomp filter transaction * @param col the filter collection + * @param user true if initiated by a user * * This function aborts the most recent seccomp filter transaction. * */ -void db_col_transaction_abort(struct db_filter_col *col) +void db_col_transaction_abort(struct db_filter_col *col, bool user) { int iter; unsigned int filter_cnt; struct db_filter **filters; struct db_filter_snap *snap; - if (col->snapshots == NULL) + snap = col->snapshots; + if (snap == NULL) + return; + + /* replace the current filter with the last snapshot, skipping shadow + * snapshots are they are duplicates of the current snapshot */ + if (snap->shadow) { + struct db_filter_snap *tmp = snap; + snap = snap->next; + _db_snap_release(tmp); + } + + if (snap->user != user) return; - /* replace the current filter with the last snapshot */ - snap = col->snapshots; col->snapshots = snap->next; + filter_cnt = col->filter_cnt; filters = col->filters; col->filter_cnt = snap->filter_cnt; @@ -2537,13 +2553,14 @@ void db_col_transaction_abort(struct db_filter_col *col) /** * Commit the top most seccomp filter transaction * @param col the filter collection + * @param user true if initiated by a user * * This function commits the most recent seccomp filter transaction and * attempts to create a shadow transaction that is a duplicate of the current * filter to speed up future transactions. * */ -void db_col_transaction_commit(struct db_filter_col *col) +void db_col_transaction_commit(struct db_filter_col *col, bool user) { int rc; unsigned int iter; @@ -2559,12 +2576,16 @@ void db_col_transaction_commit(struct db_filter_col *col) if (snap->shadow) { /* leave the shadow intact, but drop the next snapshot */ if (snap->next) { - snap->next = snap->next->next; - _db_snap_release(snap->next); + struct db_filter_snap *tmp = snap->next; + snap->next = tmp->next; + _db_snap_release(tmp); } return; } + if (snap->user != user) + return; + /* adjust the number of filters if needed */ if (col->filter_cnt > snap->filter_cnt) { unsigned int tmp_i; @@ -2593,8 +2614,8 @@ void db_col_transaction_commit(struct db_filter_col *col) * at the cost of a not reaping all the memory possible */ do { - _db_release(snap->filters[snap->filter_cnt--]); - } while (snap->filter_cnt > col->filter_cnt); + _db_release(snap->filters[--snap->filter_cnt]); + } while (col->filter_cnt < snap->filter_cnt); } /* loop through each filter and update the rules on the snapshot */ diff --git a/src/db.h b/src/db.h index 906a857c..a7632fbc 100644 --- a/src/db.h +++ b/src/db.h @@ -145,6 +145,7 @@ struct db_filter_snap { struct db_filter **filters; unsigned int filter_cnt; bool shadow; + bool user; struct db_filter_snap *next; }; @@ -215,9 +216,9 @@ int db_col_rule_add(struct db_filter_col *col, int db_col_syscall_priority(struct db_filter_col *col, int syscall, uint8_t priority); -int db_col_transaction_start(struct db_filter_col *col); -void db_col_transaction_abort(struct db_filter_col *col); -void db_col_transaction_commit(struct db_filter_col *col); +int db_col_transaction_start(struct db_filter_col *col, bool user); +void db_col_transaction_abort(struct db_filter_col *col, bool user); +void db_col_transaction_commit(struct db_filter_col *col, bool user); int db_col_precompute(struct db_filter_col *col); void db_col_precompute_reset(struct db_filter_col *col); diff --git a/src/python/libseccomp.pxd b/src/python/libseccomp.pxd index a5d6c4a2..f2784881 100644 --- a/src/python/libseccomp.pxd +++ b/src/python/libseccomp.pxd @@ -173,6 +173,10 @@ cdef extern from "seccomp.h": int seccomp_export_bpf_mem(const scmp_filter_ctx ctx, void *buf, size_t *len) + int seccomp_transaction_start(const scmp_filter_ctx ctx) + void seccomp_transaction_reject(const scmp_filter_ctx ctx) + int seccomp_transaction_commit(const scmp_filter_ctx ctx) + int seccomp_precompute(const scmp_filter_ctx ctx) # kate: syntax python; diff --git a/src/python/seccomp.pyx b/src/python/seccomp.pyx index 77129844..7e03dc0e 100644 --- a/src/python/seccomp.pyx +++ b/src/python/seccomp.pyx @@ -1079,6 +1079,34 @@ cdef class SyscallFilter: raise RuntimeError(str.format("Library error (errno = {0})", rc)) return program + def start_transaction(self): + """ Start a transaction. + + Description: + Start a transaction for modifying the seccomp filter. + """ + rc = libseccomp.seccomp_transaction_start(self._ctx) + if rc != 0: + raise RuntimeError(str.format("Library error (errno = {0})", rc)) + + def reject_transaction(self): + """ Reject a transaction. + + Description: + Reject the current seccomp filter transaction. + """ + libseccomp.seccomp_transaction_reject(self._ctx) + + def commit_transaction(self): + """ Commit a transaction. + + Description: + Commit the current seccomp filter transaction. + """ + rc = libseccomp.seccomp_transaction_commit(self._ctx) + if rc != 0: + raise RuntimeError(str.format("Library error (errno = {0})", rc)) + def precompute(self): """ Precompute the seccomp filter. diff --git a/tests/.gitignore b/tests/.gitignore index de6efd1f..1f2f166f 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -68,3 +68,4 @@ util.pyc 58-live-tsync_notify 59-basic-empty_binary_tree 60-sim-precompute +61-sim-transactions diff --git a/tests/61-sim-transactions.c b/tests/61-sim-transactions.c new file mode 100644 index 00000000..aa03d862 --- /dev/null +++ b/tests/61-sim-transactions.c @@ -0,0 +1,135 @@ +/** + * Seccomp Library test program + * + * Copyright (c) 2023 Microsoft Corporation + * Author: Paul Moore + */ + +/* + * This library is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License as + * published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see . + */ + +#include +#include + +#include + +#include "util.h" + +int main(int argc, char *argv[]) +{ + int rc; + int i, j; + struct util_options opts; + scmp_filter_ctx ctx = NULL; + + rc = util_getopt(argc, argv, &opts); + if (rc < 0) + goto out; + + ctx = seccomp_init(SCMP_ACT_ALLOW); + if (ctx == NULL) + return ENOMEM; + + rc = seccomp_transaction_start(ctx); + if (rc != 0) + goto out; + rc = seccomp_rule_add_exact(ctx, SCMP_ACT_KILL, 1000, 0); + if (rc != 0) + goto out; + rc = seccomp_transaction_commit(ctx); + if (rc != 0) + goto out; + + rc = seccomp_rule_add_exact(ctx, SCMP_ACT_KILL, 1001, 0); + if (rc != 0) + goto out; + + rc = seccomp_transaction_start(ctx); + if (rc != 0) + goto out; + for (i = 1; i <= 10; i++) { + for (j = 0; j <= i; j++) { + rc = seccomp_transaction_start(ctx); + if (rc != 0) + goto out; + } + + rc = seccomp_rule_add_exact(ctx, SCMP_ACT_KILL, 1100 + i, 0); + if (rc != 0) + goto out; + + if (i % 5) { + for (j = 0; j <= i; j++) { + rc = seccomp_transaction_commit(ctx); + if (rc != 0) + goto out; + } + } else { + for (j = 0; j <= i; j++) + seccomp_transaction_reject(ctx); + } + } + rc = seccomp_transaction_commit(ctx); + if (rc != 0) + goto out; + + rc = seccomp_transaction_start(ctx); + if (rc != 0) + goto out; + for (i = 1; i <= 10; i++) { + for (j = 0; j <= i; j++) { + rc = seccomp_transaction_start(ctx); + if (rc != 0) + goto out; + } + + rc = seccomp_rule_add_exact(ctx, SCMP_ACT_KILL, 1200 + i, 0); + if (rc != 0) + goto out; + + if (i % 5) { + for (j = 0; j <= i; j++) { + rc = seccomp_transaction_commit(ctx); + if (rc != 0) + goto out; + } + } else { + for (j = 0; j <= i; j++) + seccomp_transaction_reject(ctx); + } + } + seccomp_transaction_reject(ctx); + + rc = seccomp_transaction_start(ctx); + if (rc != 0) + goto out; + rc = seccomp_rule_add_exact(ctx, SCMP_ACT_KILL, 1002, 0); + if (rc != 0) + goto out; + rc = seccomp_transaction_commit(ctx); + if (rc != 0) + goto out; + + rc = seccomp_rule_add_exact(ctx, SCMP_ACT_KILL, 1003, 0); + if (rc != 0) + goto out; + + rc = util_filter_output(&opts, ctx); + if (rc) + goto out; + +out: + seccomp_release(ctx); + return (rc < 0 ? -rc : rc); +} diff --git a/tests/61-sim-transactions.py b/tests/61-sim-transactions.py new file mode 100755 index 00000000..8ba16224 --- /dev/null +++ b/tests/61-sim-transactions.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +# +# Seccomp Library test program +# +# Copyright (c) 2023 Microsoft Corporation +# Author: Paul Moore +# + +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License as +# published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, see . +# + +import argparse +import sys + +import util + +from seccomp import * + +def test(args): + f = SyscallFilter(ALLOW) + f.start_transaction() + f.add_rule_exactly(KILL, 1000) + f.commit_transaction() + f.add_rule_exactly(KILL, 1001) + + f.start_transaction() + for i in range(1, 11): + for j in range(0, i + 1): + f.start_transaction() + f.add_rule_exactly(KILL, 1100 + i) + if (i % 5): + for j in range(0, i + 1): + f.commit_transaction() + else: + for j in range(0, i + 1): + f.reject_transaction() + f.commit_transaction() + + f.start_transaction() + for i in range(1, 11): + for j in range(0, i + 1): + f.start_transaction() + f.add_rule_exactly(KILL, 1200 + i) + if (i % 5): + for j in range(0, i + 1): + f.commit_transaction() + else: + for j in range(0, i + 1): + f.reject_transaction() + f.reject_transaction() + + f.start_transaction() + f.add_rule_exactly(KILL, 1002) + f.commit_transaction() + f.add_rule_exactly(KILL, 1003) + return f + +args = util.get_opt() +ctx = test(args) +util.filter_output(args, ctx) + +# kate: syntax python; +# kate: indent-mode python; space-indent on; indent-width 4; mixedindent off; diff --git a/tests/61-sim-transactions.tests b/tests/61-sim-transactions.tests new file mode 100644 index 00000000..64bfdfa5 --- /dev/null +++ b/tests/61-sim-transactions.tests @@ -0,0 +1,29 @@ +# +# libseccomp regression test automation data +# +# Copyright (c) 2023 Microsoft Corporation +# Author: Paul Moore +# + +test type: bpf-sim + +# Testname Arch Syscall Arg0 Arg1 Arg2 Arg3 Arg4 Arg5 Result +61-sim-transactions all 1000 N N N N N N KILL +61-sim-transactions all 1001 N N N N N N KILL +61-sim-transactions all 1002 N N N N N N KILL +61-sim-transactions all 1003 N N N N N N KILL +61-sim-transactions all 1101-1104 N N N N N N KILL +61-sim-transactions all 1105 N N N N N N ALLOW +61-sim-transactions all 1106-1109 N N N N N N KILL +61-sim-transactions all 1110 N N N N N N ALLOW +61-sim-transactions all 1200-1210 N N N N N N ALLOW + +test type: bpf-sim-fuzz + +# Testname StressCount +61-sim-transactions 5 + +test type: bpf-valgrind + +# Testname +61-sim-transactions diff --git a/tests/Makefile.am b/tests/Makefile.am index 07bea2e7..22dd6397 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -95,7 +95,8 @@ check_PROGRAMS = \ 57-basic-rawsysrc \ 58-live-tsync_notify \ 59-basic-empty_binary_tree \ - 60-sim-precompute + 60-sim-precompute \ + 61-sim-transactions EXTRA_DIST_TESTPYTHON = \ util.py \ @@ -156,7 +157,8 @@ EXTRA_DIST_TESTPYTHON = \ 57-basic-rawsysrc.py \ 58-live-tsync_notify.py \ 59-basic-empty_binary_tree.py \ - 60-sim-precompute.py + 60-sim-precompute.py \ + 61-sim-transactions.py EXTRA_DIST_TESTCFGS = \ 01-sim-allow.tests \ @@ -218,7 +220,8 @@ EXTRA_DIST_TESTCFGS = \ 57-basic-rawsysrc.tests \ 58-live-tsync_notify.tests \ 59-basic-empty_binary_tree.tests \ - 60-sim-precompute.tests + 60-sim-precompute.tests \ + 61-sim-transactions.tests EXTRA_DIST_TESTSCRIPTS = \ 38-basic-pfc_coverage.sh 38-basic-pfc_coverage.pfc \