From 6adf397c7b78fe210ab627df6d7c85a9739121a8 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Wed, 20 Sep 2023 13:31:39 -0400 Subject: [PATCH] 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 --- doc/man/man3/seccomp_transaction_start.3 | 134 +++++++++++++++++++++++ include/seccomp.h.in | 29 +++++ src/api.c | 39 +++++++ src/db.c | 24 +++- src/db.h | 7 +- src/python/libseccomp.pxd | 4 + src/python/seccomp.pyx | 28 +++++ tests/.gitignore | 1 + tests/61-sim-transactions.c | 108 ++++++++++++++++++ tests/61-sim-transactions.py | 61 +++++++++++ tests/61-sim-transactions.tests | 28 +++++ tests/Makefile.am | 9 +- 12 files changed, 460 insertions(+), 12 deletions(-) create mode 100644 doc/man/man3/seccomp_transaction_start.3 create mode 100644 tests/61-sim-transactions.c create mode 100755 tests/61-sim-transactions.py create mode 100644 tests/61-sim-transactions.tests 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 f4fc62ca..f17c13ed 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,11 +2506,12 @@ 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; @@ -2524,6 +2529,10 @@ void db_col_transaction_abort(struct db_filter_col *col) snap = snap->next; _db_snap_release(tmp); } + + if (snap->user != user) + return; + col->snapshots = snap->next; filter_cnt = col->filter_cnt; @@ -2544,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; @@ -2561,6 +2571,8 @@ void db_col_transaction_commit(struct db_filter_col *col) snap = col->snapshots; if (snap == NULL) return; + if (snap->user != user) + return; /* check for a shadow set by a higher transaction commit */ if (snap->shadow) { 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..40be1e75 --- /dev/null +++ b/tests/61-sim-transactions.c @@ -0,0 +1,108 @@ +/** + * 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; + 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..a8f0099f --- /dev/null +++ b/tests/61-sim-transactions.py @@ -0,0 +1,61 @@ +#!/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.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..6e5c6341 --- /dev/null +++ b/tests/61-sim-transactions.tests @@ -0,0 +1,28 @@ +# +# 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 + +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 \