From: sashiko-bot@kernel.org
To: "HARIHARA SUDHAN. R" <harisudhan@armoriq.io>,
linux-security-module@vger.kernel.org,
"Paul Moore" <paul@paul-moore.com>, "Kees Cook" <kees@kernel.org>,
"James Morris" <jmorris@namei.org>,
"Serge E . Hallyn" <serge@hallyn.com>
Cc: "Andrew Morton" <akpm@linux-foundation.org>,
"Jonathan Corbet" <corbet@lwn.net>,
"Randy Dunlap" <rdunlap@infradead.org>,
"Peter Zijlstra" <peterz@infradead.org>,
"Ingo Molnar" <mingo@redhat.com>,
"Steven Rostedt" <rostedt@goodmis.org>,
"Liam R . Howlett" <liam@infradead.org>,
"Lorenzo Stoakes" <ljs@kernel.org>,
"Vlastimil Babka" <vbabka@kernel.org>,
"David Hildenbrand" <david@kernel.org>,
"Mike Rapoport" <rppt@kernel.org>,
"Michal Hocko" <mhocko@suse.com>, "Mel Gorman" <mgorman@suse.de>,
"Suren Baghdasaryan" <surenb@google.com>,
"Ben Segall" <bsegall@google.com>,
"Dietmar Eggemann" <dietmar.eggemann@arm.com>,
"Juri Lelli" <juri.lelli@redhat.com>,
"K Prateek Nayak" <kprateek.nayak@amd.com>,
"Valentin Schneider" <vschneid@redhat.com>,
"Vincent Guittot" <vincent.guittot@linaro.org>,
"Shuah Khan" <skhan@linuxfoundation.org>,
"Ketan Bhardwaj" <ketan@armoriq.io>,
bpf@vger.kernel.org, linux-mm@kvack.org
Subject: Re: [RFC PATCH v3 4/6] security: kap: add the securityfs control plane
Date: Tue, 10 Nov 2026 20:47:00 +0000 [thread overview]
Message-ID: <sashiko-outbox-207728@kernel.org> (raw)
In-Reply-To: <20261110202246.274-5-harisudhan@armoriq.io>
Thank you for your contribution! Sashiko AI review found 9 potential
issues to consider:
- [Medium] security: kap: dentry reference leak on child files in
kapfs_mkdir()
- [Medium] security: kap: unattached kapfs_mnt resolves as root-relative
in d_path()
- [Medium] security: kap: missing superblock ops and blocksize in
kapfs_fill_super()
- [Medium] security: kap: data race on kap_task_state in
kap_bind_pid_locked()
- [Medium] security: kap: race between kap_bind_pid_locked() and
copy_process()
- [Medium] security: kap: kap_authority_prepare() bounds predicates by
next kind not '}'
- [Low] security: kap: unsanitized lineage_root in kapfs_write()
corrupts output
- [Low] security: kap: kapfs_write() silently succeeds on newline-only
writes
- [Low] security: kap: unchecked u64 overflow in
kap_parse_transition_sequence()
--
commit 9864f112a0785ec3a3604d782064cf536e36889c
Author: HARIHARA SUDHAN. R <harisudhan@armoriq.io>
security: kap: add the securityfs control plane
This commit adds the KAP securityfs control plane at /sys/kernel/security/kap
and mounts an internal kapfs filesystem for managing igroups and their control
files.
> diff --git a/security/kap/kapfs.c b/security/kap/kapfs.c
> new file mode 100644
> index 0000000000000..b182ff24bb8c4
> --- /dev/null
> +++ b/security/kap/kapfs.c
[ ... ]
> +static ssize_t kapfs_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos)
> +{
[ ... ]
> + tmp = memdup_user_nul(buf, len);
> + if (IS_ERR(tmp))
> + return PTR_ERR(tmp);
> + kap_trim_newline(tmp);
> + if (!tmp[0]) {
> + kfree(tmp);
> + return len;
> + }
[Severity: Low]
Should writing a newline-only string to a control file really succeed without
doing anything?
If a script writes an empty line (for example, echo "$PID" > kap.procs when
PID is unset), kapfs_write() returns len without validating the input or
updating any state, and this early return also prevents clearing
kap.lineage_root via a single-file write even though kap.install accepts an
empty lineage_root=.
> +
> + /* Parse, allocate and compile first: kap_lock is a spinlock. */
> + switch (ctx->kind) {
> + case KAP_FILE_PROCS:
> + rc = kstrtoint(tmp, 10, &pid);
> + if (!rc) {
> + entry = kzalloc_obj(*entry, GFP_KERNEL);
> + if (!entry)
> + rc = -ENOMEM;
> + }
> + break;
> + case KAP_FILE_LINEAGE_ROOT:
> + if (strlen(tmp) > KAP_MAX_ROOT_LEN)
> + rc = -EINVAL;
> + break;
[Severity: Low]
Can an unsanitized lineage_root corrupt kap.igfd and kap.events output?
KAP_FILE_LINEAGE_ROOT and kap_install_parse() only check the length of the
string, so embedded newlines break the single-line format of kap.igfd in
kapfs_show(), and quotes or backslashes are interpolated unescaped into the
"root" JSON field by kap_append_event_locked():
security/kap/igroup.c:kap_append_event_locked() {
...
len = scnprintf(line, sizeof(line),
"{\"ts_ns\":%llu,\"igroup\":\"%s\",\"decision\":\"%s\",\"operation\":\"%s\",\"target\":\"%s\",\"reason\":\"%s\",\"epoch\":%llu,\"root\":\"%s\",\"pid\":%d}\n",
now, igroup->name, allowed ? "allow" : "deny",
kap_hook_name(kind), escaped_target, reason, igroup->epoch,
igroup->lineage_root, task_pid_nr(current));
...
}
> + case KAP_FILE_INSTALL:
> + rc = kap_install_parse(tmp, &req);
> + if (!rc)
> + rc = kap_authority_prepare(req.authority, &compiled);
> + break;
> + case KAP_FILE_AUTHORITY:
> + rc = kap_authority_prepare(tmp, &compiled);
> + break;
[Severity: Medium]
Can kap_authority_prepare() bleed one predicate's allow or deny array into
the preceding predicate?
Both kap_validate_authority_payload() and kap_compile_kind_array() scan
backward from "kind": to '{' to find predicate_start, so keys appearing before
"kind" inside a predicate object are supported, but they set predicate_end to
the next "kind": rather than the closing '}' of the current predicate:
security/kap/igroup.c:kap_compile_kind_array() {
...
while ((predicate = kap_next_predicate(predicate, end, kind_name))) {
const char *next = strstr(predicate + 1, "\"kind\":");
const char *predicate_start = predicate;
const char *predicate_end = next && next < end ? next : end;
int rc;
while (predicate_start > auth && *predicate_start != '{')
predicate_start--;
rc = kap_compile_array(compiled, set, predicate_start,
predicate_end, array_name);
...
}
If one predicate omits "allow" or "deny" and the next predicate places its
"allow":[...] or "deny":[...] before its "kind": key, the slice
[predicate_start, predicate_end) spans across the boundary between the two
objects, causing validation to accept the malformed document and compiling the
second predicate's patterns into the first predicate's hook kind.
> + case KAP_FILE_AUTHORITY_EPOCH:
> + case KAP_FILE_EXPIRES_AT:
> + rc = kstrtou64(tmp, 10, &value);
> + break;
> + case KAP_FILE_TRANSITION:
> + rc = kap_parse_transition_sequence(tmp, &value);
> + break;
[Severity: Low]
Can kap_parse_transition_sequence() silently wrap on u64 overflow?
Unlike authority_epoch and expires_at, which use kstrtou64(),
kap_parse_transition_sequence() accumulates decimal digits without checking
for overflow:
security/kap/igroup.c:kap_parse_transition_sequence() {
...
while (*cursor >= '0' && *cursor <= '9') {
seen_digit = true;
value = value * 10 + (*cursor - '0');
cursor++;
}
...
}
Writing a sequence number of 20 or more digits to kap.transition or
kap.install wraps modulo 2^64, which can corrupt igroup->last_sequence if the
wrapped value exceeds the previous sequence number.
[ ... ]
> + spin_lock(&kap_lock);
> + switch (ctx->kind) {
> + case KAP_FILE_PROCS:
> + rc = kap_bind_pid_locked(igroup, pid, &entry);
> + break;
[Severity: Medium]
Is there a data race on struct kap_task_state between kap_bind_pid_locked()
and kap_ptrace_target()?
kap_bind_pid_locked() sets state->active = true before populating state->epoch
and state->igroup_name, while kap_ptrace_target() reads those fields without
holding kap_lock:
CPU0 CPU1
kapfs_write()
spin_lock(&kap_lock)
kap_bind_pid_locked()
state->active = true
kap_ptrace_access_check()
kap_ptrace_target()
if (child_state->active)
strcmp(..., child_state->igroup_name)
<-- reads empty or torn igroup_name
state->epoch = igroup->epoch
strscpy(state->igroup_name, ...)
A concurrent ptrace check can therefore misclassify same-igroup vs
cross-igroup ptrace operations and log a corrupted target in kap.events.
[Severity: Medium]
Can a thread or child being created in copy_process() escape binding when
kap_bind_pid_locked() runs concurrently?
kap_task_alloc() copies the parent's kap_task_state early in copy_process(),
long before the new task is linked onto signal->thread_head under siglock:
CPU0 CPU1
copy_process()
security_task_alloc()
kap_task_alloc()
spin_lock(&kap_lock)
/* old->active is false */
spin_unlock(&kap_lock)
kapfs_write()
spin_lock(&kap_lock)
kap_bind_pid_locked()
for_each_thread(task, t)
state->active = true
spin_unlock(&kap_lock)
write_lock_irq(&tasklist_lock)
list_add_tail_rcu(&p->thread_node,
&p->signal->thread_head)
<-- p runs with state->active == false
The new thread or child process is missed by both kap_task_alloc() and
for_each_thread(), leaving it running unbound even though writing the process
PID to kap.procs succeeded.
[ ... ]
> +static void kapfs_instantiate(struct dentry *dentry, struct inode *inode)
> +{
> + d_make_persistent(dentry, inode);
> +}
[ ... ]
> +/* Returns NULL on success: the passed dentry is the one instantiated. */
> +static struct dentry *kapfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
> + struct dentry *dentry, umode_t mode)
> +{
[ ... ]
> + for (i = 0; i < KAP_FILE_COUNT; i++) {
> + files[i] = d_alloc_name(dentry, kap_file_names[i]);
> + inodes[i] = kapfs_make_inode(dir->i_sb, S_IFREG | kap_file_modes[i]);
[ ... ]
> + }
[ ... ]
> + inode_lock(inode);
> + kapfs_instantiate(dentry, inode);
> + igroup->dentry = dentry;
> + for (i = 0; i < KAP_FILE_COUNT; i++)
> + kapfs_instantiate(files[i], inodes[i]);
> + inode_unlock(inode);
> + return NULL;
[Severity: Medium]
Does kapfs_mkdir() leak a dentry reference on each entry in files[]?
d_alloc_name() returns each child dentry with a refcount of 1, and
kapfs_instantiate() -> d_make_persistent() takes an additional reference via
dget_dlock(). Unlike the directory dentry argument whose lookup reference is
dropped by the VFS caller, files[i] is local to kapfs_mkdir() and needs
dput(files[i]) after kapfs_instantiate().
[ ... ]
> +static int kapfs_fill_super(struct super_block *sb, struct fs_context *fc)
> +{
> + struct inode *inode;
> +
> + sb->s_magic = KAPFS_MAGIC;
> + inode = kapfs_make_inode(sb, S_IFDIR | 0700);
> + if (!inode)
> + return -ENOMEM;
> + inode->i_op = &kapfs_root_inode_ops;
> + inode->i_fop = &simple_dir_operations;
> + inc_nlink(inode);
> + sb->s_root = d_make_root(inode);
> + if (!sb->s_root)
> + return -ENOMEM;
> + return 0;
> +}
[Severity: Medium]
Should kapfs_fill_super() initialize sb->s_op, sb->s_blocksize, and
sb->s_blocksize_bits?
Without sb->s_op = &simple_super_operations, statfs() and fstatfs() on
/sys/kernel/security/kap/igroups and its entries fail with -ENOSYS, and
leaving sb->s_blocksize and sb->s_blocksize_bits at zero causes stat() to
report st_blksize = 1.
[ ... ]
> +static const char *kap_igroups_get_link(struct dentry *dentry,
> + struct inode *inode,
> + struct delayed_call *done)
> +{
> + struct path path;
> + int rc;
> +
> + if (!dentry)
> + return ERR_PTR(-ECHILD);
> + path.mnt = mntget(kapfs_mnt);
> + path.dentry = dget(kapfs_mnt->mnt_root);
> + rc = nd_jump_link(&path);
> + return ERR_PTR(rc);
> +}
[Severity: Medium]
Doesn't jumping onto the unattached kapfs_mnt cause d_path() in KAP's LSM
hooks to resolve paths under /sys/kernel/security/kap/igroups as
/<igroup>/<file>?
kapfs_mnt is created with kern_mount() and is not attached to any mountpoint,
so when kap_file_target(), kap_lsm_file_path_alloc(), or
kap_lsm_check_dir_entry() calls d_path(), walk-back stops at
kapfs_mnt->mnt_root and omits the /sys/kernel/security/kap/igroups prefix:
security/kap/kap_lsm.c:kap_lsm_file_path_alloc() {
...
path = d_path(&file->f_path, buf, PATH_MAX);
if (IS_ERR(path)) {
kfree(buf);
return NULL;
}
...
}
File rules targeting /sys or /sys/kernel/security/kap will therefore not match
accesses under igroups/, and kap.events will log root-relative target paths.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261110202246.274-1-harisudhan@armoriq.io?part=4
next prev parent reply other threads:[~2026-11-10 20:47 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 20:22 [RFC PATCH v3 0/6] security: introduce Kernel Assurance Plane HARIHARA SUDHAN. R
2026-11-10 20:22 ` [RFC PATCH v3 1/6] docs: security: Describe the Kernel Assurance Plane design HARIHARA SUDHAN. R
2026-11-10 20:33 ` sashiko-bot
2026-11-10 20:22 ` [RFC PATCH v3 2/6] security: kap: add the LSM ID and internal headers HARIHARA SUDHAN. R
2026-11-10 20:30 ` sashiko-bot
2026-11-10 20:22 ` [RFC PATCH v3 3/6] security: add the Kernel Assurance Plane LSM HARIHARA SUDHAN. R
2026-11-10 20:51 ` sashiko-bot
2026-11-10 20:22 ` [RFC PATCH v3 4/6] security: kap: add the securityfs control plane HARIHARA SUDHAN. R
2026-11-10 20:47 ` sashiko-bot [this message]
2026-11-10 20:22 ` [RFC PATCH v3 5/6] kernel: add KAP checks for unshare and setns HARIHARA SUDHAN. R
2026-11-10 20:36 ` sashiko-bot
2026-11-10 20:22 ` [RFC PATCH v3 6/6] MAINTAINERS: add an entry for the Kernel Assurance Plane HARIHARA SUDHAN. R
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-207728@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=bpf@vger.kernel.org \
--cc=bsegall@google.com \
--cc=corbet@lwn.net \
--cc=david@kernel.org \
--cc=dietmar.eggemann@arm.com \
--cc=harisudhan@armoriq.io \
--cc=jmorris@namei.org \
--cc=juri.lelli@redhat.com \
--cc=kees@kernel.org \
--cc=ketan@armoriq.io \
--cc=kprateek.nayak@amd.com \
--cc=liam@infradead.org \
--cc=linux-mm@kvack.org \
--cc=linux-security-module@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=mgorman@suse.de \
--cc=mhocko@suse.com \
--cc=mingo@redhat.com \
--cc=paul@paul-moore.com \
--cc=peterz@infradead.org \
--cc=rdunlap@infradead.org \
--cc=rostedt@goodmis.org \
--cc=rppt@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=serge@hallyn.com \
--cc=skhan@linuxfoundation.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=vincent.guittot@linaro.org \
--cc=vschneid@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox