From e8c75db4dd9bf60d1fdc3eed6a895321a97a0d9a Mon Sep 17 00:00:00 2001 From: zbarsky-openai Date: Mon, 24 Aug 2026 17:02:22 -0400 Subject: [PATCH] actiondfs: use mount credentials for Landlock-safe backing opens Landlock-confined programs cannot read files from actiondfs even when their policy explicitly grants the visible actiondfs path: ```text PermissionError: [Errno 13] Permission denied: '/execroot/.../python3.12/encodings/__init__.py' ``` The visible path is authorized, but actiondfs then opens its hidden ext4 CAS or staging backing file with the already-confined task credentials. Landlock evaluates that unrelated underlying path and rejects the internal open. Follow Linux overlayfs's existing model: retain the mount creator's credentials, temporarily override task credentials only while opening internal directory, CAS, and staged backing files, then restore the caller immediately. The original VFS/Landlock check still applies to the user-visible actiondfs path. Release the saved credential when the superblock is destroyed. Validated by rebuilding and booting the actual ARM64 guest kernel through actiond (`f16f4f3f-df24-4954-8a61-793858c17bc7`, 943 real remote actions), then rerunning both production sandbox seccomp cases with a real Landlock policy (`08b932ab-9f76-4129-a0bb-3bba52793ea6`). The complete sandbox suite advanced to 178 passing tests; remaining failures are independent guest capability gaps. --- kernel/actiondfs/actiondfs.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/kernel/actiondfs/actiondfs.c b/kernel/actiondfs/actiondfs.c index fa0fe6c..400fc8b 100644 --- a/kernel/actiondfs/actiondfs.c +++ b/kernel/actiondfs/actiondfs.c @@ -136,6 +136,7 @@ struct actiondfs_node { struct actiondfs_sb_info { struct path cas_path; struct path stage_path; + const struct cred *creator_cred; u8 root_hash[ACTIONDFS_HASH_LEN]; }; @@ -1116,8 +1117,11 @@ static struct file *actiondfs_open_directory_blob(struct actiondfs_sb_info *sbi, if (IS_ERR(real_path.dentry)) { file = ERR_CAST(real_path.dentry); } else { + const struct cred *old_cred = override_creds(sbi->creator_cred); + file = kernel_file_open(&real_path, O_RDONLY | O_NONBLOCK, current_cred()); + revert_creds(old_cred); dput(real_path.dentry); } if (!IS_ERR(file)) @@ -1167,8 +1171,13 @@ static struct file *actiondfs_open_backing_cas_blob(struct actiondfs_sb_info *sb } open_start = actiondfs_stat_time_start(); - file = backing_file_open(user_path, O_RDONLY, &real_path, - current_cred()); + { + const struct cred *old_cred = override_creds(sbi->creator_cred); + + file = backing_file_open(user_path, O_RDONLY, &real_path, + current_cred()); + revert_creds(old_cred); + } actiondfs_stat_add_elapsed(ACTIONDFS_STAT_BLOB_OPEN_BACKING_FILE_NS, open_start); dput(real_path.dentry); @@ -1226,8 +1235,13 @@ static struct file *actiondfs_open_staged_backing(struct actiondfs_sb_info *sbi, } phase_start = actiondfs_stat_time_start(); - file = backing_file_open(file_user_path(actiondfs_file), flags, - &real_path, current_cred()); + { + const struct cred *old_cred = override_creds(sbi->creator_cred); + + file = backing_file_open(file_user_path(actiondfs_file), flags, + &real_path, current_cred()); + revert_creds(old_cred); + } actiondfs_stat_add_elapsed(ACTIONDFS_STAT_STAGE_BACKING_OPEN_FILE_NS, phase_start); out: @@ -3403,6 +3417,8 @@ static void actiondfs_put_super(struct super_block *sb) path_put(&sbi->cas_path); if (sbi->stage_path.dentry) path_put(&sbi->stage_path); + if (sbi->creator_cred) + put_cred(sbi->creator_cred); kfree(sbi); sb->s_fs_info = NULL; } @@ -3427,6 +3443,7 @@ static int actiondfs_fill_super(struct super_block *sb, struct fs_context *fc) sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); if (!sbi) return -ENOMEM; + sbi->creator_cred = get_current_cred(); sb->s_fs_info = sbi; sb->s_magic = ACTIONDFS_MAGIC;