]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nsresourced: fix BPF loading when using kernel compiled with Clang
authorClayton Craft <clayton@craftyguy.net>
Fri, 20 Mar 2026 00:08:31 +0000 (17:08 -0700)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 20 Mar 2026 14:27:08 +0000 (15:27 +0100)
This fixes an issue where nsresourced fails to load BPF on kernels
compiled with Clang (this output was from v259):

    $ sudo env SYSTEMD_LOG_LEVEL=debug /usr/lib/systemd/systemd-nsresourced
    ; int BPF_PROG(userns_restrict_path_chown, struct path *path, void* uid, void *gid, int ret) { @ userns-restrict.bpf.c:134
    ...
    ; return validate_path(path, ret); @ userns-restrict.bpf.c:135
    ...
    ; static int validate_path(const struct path *path, int ret) { @ userns-restrict.bpf.c:120
    ...
    ; task = (struct task_struct*) bpf_get_current_task_btf(); @ userns-restrict.bpf.c:84
    ...
    ; task_userns = task->cred->user_ns; @ userns-restrict.bpf.c:85
    ...
    R2 invalid mem access 'rcu_ptr_or_null_'

When Clang is used (which sets CONFIG_PAHOLE_HAS_BTF_TAG), btf_type_tag
support is enabled. As a result, an rcu type tag is added to
task_struct::cred:

    $ bpftool btf dump file /sys/kernel/btf/vmlinux | grep "STRUCT 'task_struct'"
    [459] STRUCT 'task_struct' size=4672 vlen=242

    $ bpftool btf dump file /sys/kernel/btf/vmlinux | grep -A200 "^\[459\] STRUCT 'task_struct'" | grep cred
    'ptracer_cred' type_id=802 bits_offset=14528
    'real_cred' type_id=802 bits_offset=14592
    'cred' type_id=802 bits_offset=14656

    $ bpftool btf dump file /sys/kernel/btf/vmlinux | grep '^\[802\]'
    [802] PTR '(anon)' type_id=801

    $ bpftool btf dump file /sys/kernel/btf/vmlinux | grep '^\[801\]'
    [801] TYPE_TAG 'rcu' type_id=803

Since the struct ptr *could* be null, we have to add a null pointer
check to satisfy the bpf verifier.

src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c

index 25d609bf38fc83bc7c4c4f3ad5b9f923124d0454..d70493fe7af5fc20cd8caf150e64adcd3c50ce77 100644 (file)
@@ -119,6 +119,7 @@ static int userns_owns_mount(struct user_namespace *userns, struct vfsmount *v)
 static int validate_mount(struct vfsmount *v, int ret) {
         struct user_namespace *task_userns;
         unsigned task_userns_inode;
+        const struct cred *cred;
         struct task_struct *task;
         void *mnt_id_map;
         struct mount *m;
@@ -129,7 +130,10 @@ static int validate_mount(struct vfsmount *v, int ret) {
 
         /* Get user namespace from task */
         task = (struct task_struct*) bpf_get_current_task_btf();
-        task_userns = task->cred->user_ns;
+        cred = task->cred;
+        if (!cred)
+                return -EPERM;
+        task_userns = cred->user_ns;
 
         /* fsuid/fsgid are the UID/GID in the initial user namespace, before any idmapped mounts have been
          * applied. There is no way (yet) to figure out what the UID/GID that will be written to disk will be
@@ -138,7 +142,7 @@ static int validate_mount(struct vfsmount *v, int ret) {
          * translate the transient UID range to something else. For other UIDs/GIDs, there's no need to do
          * these checks as we don't insist on idmapped mounts or such for UIDs/GIDs outside the transient
          * ranges. */
-        if (!uid_is_transient(task->cred->fsuid.val) && !uid_is_transient((uid_t) task->cred->fsgid.val))
+        if (!uid_is_transient(cred->fsuid.val) && !uid_is_transient((uid_t) cred->fsgid.val))
                 return 0;
 
         r = userns_owns_mount(task_userns, v);
@@ -170,6 +174,7 @@ SEC("lsm/path_chown")
 int BPF_PROG(userns_restrict_path_chown, struct path *path, unsigned long long uid, unsigned long long gid, int ret) {
         struct user_namespace *task_userns;
         unsigned task_userns_inode;
+        const struct cred *cred;
         struct task_struct *task;
         struct vfsmount *v;
         void *mnt_id_map;
@@ -180,7 +185,10 @@ int BPF_PROG(userns_restrict_path_chown, struct path *path, unsigned long long u
 
         /* Get user namespace from task */
         task = (struct task_struct*) bpf_get_current_task_btf();
-        task_userns = task->cred->user_ns;
+        cred = task->cred;
+        if (!cred)
+                return -EPERM;
+        task_userns = cred->user_ns;
         v = path->mnt;
 
         r = userns_owns_mount(task_userns, v);