[PATCH v2 2/3] seccomp: Allow upgrading to strict mode after enabling filters

From: Jamie Hill-Daniel

Date: Mon Jun 15 2026 - 12:23:40 EST


Add a strict_upgrade flag to seccomp, which is enabled when
SECCOMP_MODE_STRICT is set after filters have been installed. This
will run the strict mode checks after all filters have been run.

This flag blocks thread syncing, in keeping with regular
SECCOMP_MODE_STRICT.
---
include/linux/seccomp_types.h | 3 +++
kernel/seccomp.c | 55 +++++++++++++++++++------------------------
2 files changed, 27 insertions(+), 31 deletions(-)

diff --git a/include/linux/seccomp_types.h b/include/linux/seccomp_types.h
index 67ab25d7540a..d8a908cc822d 100644
--- a/include/linux/seccomp_types.h
+++ b/include/linux/seccomp_types.h
@@ -13,6 +13,8 @@ struct seccomp_filter;
* @mode: indicates one of the valid values above for controlled
* system calls available to a process.
* @dead: indicates that the process should be killed as a result of seccomp
+ * @strict_upgrade: indicates that strict mode has been enabled after filter
+ * mode has been enabled.
* @filter_count: number of seccomp filters
* @filter: must always point to a valid seccomp-filter or NULL as it is
* accessed without locking during system call entry.
@@ -23,6 +25,7 @@ struct seccomp_filter;
struct seccomp {
unsigned int mode : 2;
unsigned int dead : 1;
+ unsigned int strict_upgrade : 1;
atomic_t filter_count;
struct seccomp_filter *filter;
};
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 8f5903f72d54..92804d488114 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -13,6 +13,7 @@
* Mode 2 allows user-defined system call filters in the form
* of Berkeley Packet Filters/Linux Socket Filters.
*/
+#include "linux/seccomp.h"
#define pr_fmt(fmt) "seccomp: " fmt

#include <linux/refcount.h>
@@ -429,16 +430,6 @@ static u32 seccomp_run_filters(const struct seccomp_data *sd,
}
#endif /* CONFIG_SECCOMP_FILTER */

-static inline bool seccomp_may_assign_mode(unsigned long seccomp_mode)
-{
- assert_spin_locked(&current->sighand->siglock);
-
- if (current->seccomp.mode && current->seccomp.mode != seccomp_mode)
- return false;
-
- return true;
-}
-
void __weak arch_seccomp_spec_mitigate(struct task_struct *task) { }

static inline void seccomp_assign_mode(struct task_struct *task,
@@ -504,7 +495,8 @@ static inline pid_t seccomp_can_sync_threads(void)
if (thread->seccomp.mode == SECCOMP_MODE_DISABLED ||
(thread->seccomp.mode == SECCOMP_MODE_FILTER &&
is_ancestor(thread->seccomp.filter,
- caller->seccomp.filter)))
+ caller->seccomp.filter) &&
+ !thread->seccomp.strict_upgrade))
continue;

/* Return the first thread that cannot be synchronized. */
@@ -1259,6 +1251,7 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
struct seccomp_data sd;
struct seccomp_filter *match = NULL;
int data;
+ int recheck_result;

/*
* Make sure that any changes to mode from another thread have
@@ -1326,7 +1319,10 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
* a reload of all registers. This does not goto skip since
* a skip would have already been reported.
*/
- if (__seccomp_filter(this_syscall, true))
+ recheck_result = __seccomp_filter(this_syscall, true);
+ if (current->seccomp.strict_upgrade)
+ __secure_computing_strict(this_syscall);
+ if (recheck_result)
return -1;

return 0;
@@ -1386,6 +1382,7 @@ int __secure_computing(void)
{
int mode = current->seccomp.mode;
int this_syscall;
+ int filter_ret;

if (IS_ENABLED(CONFIG_CHECKPOINT_RESTORE) &&
unlikely(current->ptrace & PT_SUSPEND_SECCOMP))
@@ -1405,7 +1402,10 @@ int __secure_computing(void)
__secure_computing_strict(this_syscall); /* may call do_exit */
return 0;
case SECCOMP_MODE_FILTER:
- return __seccomp_filter(this_syscall, false);
+ filter_ret = __seccomp_filter(this_syscall, false);
+ if (current->seccomp.strict_upgrade)
+ __secure_computing_strict(this_syscall);
+ return filter_ret;
default:
BUG();
}
@@ -1420,30 +1420,23 @@ long prctl_get_seccomp(void)
/**
* seccomp_set_mode_strict: internal function for setting strict seccomp
*
- * Once current->seccomp.mode is non-zero, it may not be changed.
- *
- * Returns 0 on success or -EINVAL on failure.
+ * If current->seccomp.mode is SECCOMP_MODE_FILTER, this will enable
+ * strict_upgrade.
*/
-static long seccomp_set_mode_strict(void)
+static void seccomp_set_mode_strict(void)
{
const unsigned long seccomp_mode = SECCOMP_MODE_STRICT;
- long ret = -EINVAL;
-
spin_lock_irq(&current->sighand->siglock);

- if (!seccomp_may_assign_mode(seccomp_mode))
- goto out;
-
#ifdef TIF_NOTSC
disable_TSC();
#endif
- seccomp_assign_mode(current, seccomp_mode, 0);
- ret = 0;
+ if (current->seccomp.mode == SECCOMP_MODE_FILTER)
+ current->seccomp.strict_upgrade = 1;
+ else
+ seccomp_assign_mode(current, seccomp_mode, 0);

-out:
spin_unlock_irq(&current->sighand->siglock);
-
- return ret;
}

#ifdef CONFIG_SECCOMP_FILTER
@@ -1948,8 +1941,6 @@ static bool has_duplicate_listener(struct seccomp_filter *new_child)
* Every filter successfully installed will be evaluated (in reverse order)
* for each system call the task makes.
*
- * Once current->seccomp.mode is non-zero, it may not be changed.
- *
* Returns 0 on success or -EINVAL on failure.
*/
static long seccomp_set_mode_filter(unsigned int flags,
@@ -2015,7 +2006,8 @@ static long seccomp_set_mode_filter(unsigned int flags,

spin_lock_irq(&current->sighand->siglock);

- if (!seccomp_may_assign_mode(seccomp_mode))
+ /* Reachable if PTRACE_O_SUSPEND_SECCOMP has been used */
+ if (current->seccomp.mode == SECCOMP_MODE_STRICT)
goto out;

if (has_duplicate_listener(prepared)) {
@@ -2104,7 +2096,8 @@ static long do_seccomp(unsigned int op, unsigned int flags,
case SECCOMP_SET_MODE_STRICT:
if (flags != 0 || uargs != NULL)
return -EINVAL;
- return seccomp_set_mode_strict();
+ seccomp_set_mode_strict();
+ return 0;
case SECCOMP_SET_MODE_FILTER:
return seccomp_set_mode_filter(flags, uargs);
case SECCOMP_GET_ACTION_AVAIL:

--
2.54.0