Linux Security Modules development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
Cc: linux-security-module@vger.kernel.org
Subject: Re: [PATCH v3] smack: preserve low-integrity labels across file copy via whitelist
Date: Wed, 28 Oct 2026 16:37:38 +0000	[thread overview]
Message-ID: <sashiko-outbox-188170@kernel.org> (raw)
In-Reply-To: <684C81A1C8289709+20261028161521.925644-1-tangpengke@yhkylin.cn>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] smack: broken exec handling for smk_preserve in smack_bprm_creds_for_exec()
- [High] smack: missing MAY_WRITE check in smack_inode_init_security() for smk_preserve
- [High] smack: smk_preserve corrupts inode labels across OverlayFS copy-up
- [Medium] smack: silent allocation failure in smk_preserve_set()
- [Medium] smack: smack_file_open() re-records smk_preserve on O_RDONLY | O_CREAT
- [Medium] smack: unimported label rejection and unbounded write in smk_write_preserve_wl()
--

commit d4423a161be1eb02cab93ddd7185fcd574c8aaf2
Author: Tang Pengke <tangpengke@yhkylin.cn>

smack: preserve low-integrity labels across file copy via whitelist

This commit introduces a configurable whitelist (/smack/preserve-whitelist) of
low-integrity Smack labels that are recorded in a per-task LSM blob when a
process opens a whitelisted regular file read-only, so that the next inode
created by that thread inherits the preserved label instead of the process
label.

> diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c
> index fb85356266e53..4982127ce985f 100644
> --- a/security/smack/smack_access.c
> +++ b/security/smack/smack_access.c
> @@ -53,6 +53,83 @@ static u32 smack_next_secid = 10;

[ ... ]

> +void smk_preserve_set(struct smack_known *skp, bool on)
> +{
> +	struct smk_preserve_wl *e, *new = NULL;
> +
> +	if (on) {
> +		new = kzalloc_obj(*new, GFP_KERNEL);
> +		if (!new)
> +			return;
> +		new->skp = skp;
> +	}

[Severity: Medium]
When kzalloc_obj(*new, GFP_KERNEL) fails in smk_preserve_set(), this returns
early without an error code, and smk_write_preserve_wl() in
security/smack/smackfs.c then unconditionally returns count to userspace:

    smk_preserve_set(skp, on);
    kfree(data);
    return count;

Should smk_preserve_set() return an int so that smk_write_preserve_wl() can
propagate -ENOMEM to userspace when the whitelist allocation fails?

[ ... ]

> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index 8e88ac65fd7fc..094d26c943b52 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -915,6 +915,11 @@ static int smack_bprm_creds_for_exec(struct linux_binprm *bprm)
>  	struct superblock_smack *sbsp;
>  	int rc;
>  
> +#ifdef CONFIG_SECURITY_SMACK_COPYWHITELIST
> +	/* a new program starts with a clean copy record */
> +	smack_task(current)->smk_preserve = NULL;
> +#endif

[Severity: High]
Can clearing smack_task(current)->smk_preserve in smack_bprm_creds_for_exec()
allow a task to drop its pending preserve record without actually executing a
new program, or leave smk_preserve armed across exec?

In fs/exec.c:bprm_execve(), security_bprm_creds_for_exec() runs before the
bprm->is_check early return and before exec_binprm():

    retval = security_bprm_creds_for_exec(bprm);
    if (retval || bprm->is_check)
        goto out;

    retval = exec_binprm(bprm);

If a task that has opened a whitelisted low-integrity file calls
execveat(..., AT_EXECVE_CHECK) or invokes execve() on a target that fails in
smack_bprm_creds_for_exec() or exec_binprm(), smk_preserve is cleared on
current even though no exec took place, allowing subsequent file creations by
that task to use its trusted process label.

In addition, fs/exec.c:do_open_execat() and open_exec() open the executable
and any interpreter with O_LARGEFILE | O_RDONLY | __FMODE_EXEC. Because
smack_file_open() checks (file->f_flags & O_ACCMODE) == O_RDONLY without
excluding __FMODE_EXEC (or file->f_mode & FMODE_EXEC):

1. If do_open_execat() opens a whitelisted file and then fails before
   bprm_execve() (for example, at path_noexec(&file->f_path) on a noexec
   mount), smack_file_open() has already set smk_preserve on the calling task.

2. If exec_binprm() opens a script interpreter via open_exec() after
   security_bprm_creds_for_exec() has already run, smack_file_open() will
   record smk_preserve for the newly executed program if the interpreter's
   label is on the whitelist.

Should smack_file_open() ignore FMODE_EXEC opens, and should smk_preserve be
cleared when the exec commits in a bprm_committing_creds or
bprm_committed_creds hook instead of smack_bprm_creds_for_exec()?

>  	isp = smack_inode(inode);
>  	if (isp->smk_task == NULL || isp->smk_task == bsp->smk_task)
>  		return 0;
> @@ -1075,6 +1080,26 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir,
>  		}
>  	}
>  
> +#ifdef CONFIG_SECURITY_SMACK_COPYWHITELIST
> +	/*
> +	 * If a whitelisted label was recorded on file open, inherit it so
> +	 * the new file keeps the source's (low-integrity) label across the
> +	 * copy.  Transmutation takes precedence: when it applies the record
> +	 * is dropped, otherwise it would leak into a later create in a
> +	 * non-transmutable directory.
> +	 */
> +	if (trans_cred || (trans_rule && smk_inode_transmutable(dir))) {
> +		smack_task(current)->smk_preserve = NULL;
> +	} else {
> +		struct smack_known *skp = smack_task(current)->smk_preserve;
> +
> +		if (skp != NULL) {
> +			issp->smk_inode = skp;
> +			smack_task(current)->smk_preserve = NULL;
> +		}
> +	}
> +#endif

[Severity: High]
What happens here when the creating task has MAY_READ access to the
whitelisted label skp, but does not have MAY_WRITE access to skp?

In smack_file_open(), smk_preserve is recorded after checking only MAY_READ on
the source inode. When the task then creates a new file via open(dst,
O_CREAT | O_WRONLY, mode) in a writable non-transmuting directory,
may_o_create() checks MAY_WRITE | MAY_EXEC on the parent directory, and
smack_inode_init_security() assigns issp->smk_inode = skp without checking
whether the task has MAY_WRITE access to skp.

Because FMODE_CREATED is set on the struct file, fs/namei.c:do_open() sets
acc_mode = 0 before may_open():

    if (file->f_mode & FMODE_CREATED) {
        /* Don't check for write permission, don't truncate */
        open_flag &= ~O_TRUNC;
        acc_mode = 0;
    }

may_open() then passes only MAY_OPEN to inode_permission(), which
smack_inode_permission() masks out, and smack_file_open() checks only
MAY_READ. Since Smack does not hook file_permission on write(), the task
receives a writable file descriptor and can write arbitrary content to an
inode labeled skp despite policy denying it write access to skp. Meanwhile,
follow-up operations that copy utilities perform on the newly created file
(such as fchmod(), fchown(), futimens(), setxattr(), rename(), or unlink())
check MAY_WRITE against issp->smk_inode and fail with -EACCES.

[Severity: High]
Also in smack_inode_init_security(), how does smk_preserve interact with
OverlayFS copy-up and credential overrides?

When a task with a pending smk_preserve creates a file in an OverlayFS
directory whose parent is still on a lower layer,
ovl_create_object() -> ovl_copy_up() -> ovl_copy_up_workdir() calls
security_inode_copy_up() -> smack_inode_copy_up() and installs copy_up_cred
via override_creds():

    isp = smack_inode(d_inode(dentry));
    skp = isp->smk_inode;
    tsp->smk_task = skp;
    *new = new_creds;

smack_inode_copy_up() leaves tsp->smk_transmuted as NULL, so when
ovl_create_temp() creates the upper directory, smack_inode_init_security()
sees trans_cred == false, overwrites the copied-up upper directory's label
with smack_task(current)->smk_preserve, and clears smk_preserve = NULL.
Because smack_inode_copy_up_xattr() returns -ECANCELED for XATTR_NAME_SMACK,
the upper directory is permanently relabeled to the low-integrity label, and
when ovl_create_object() then creates the actual target file, smk_preserve has
already been consumed so the copied file receives the task's trusted label.

Similarly, if a task with a pending smk_preserve modifies an existing lower
file or directory on OverlayFS (for example via chmod(), truncate(), or
open(O_WRONLY)), the copy-up creation in ovl_copy_up_tmpfile() or
ovl_copy_up_workdir() consumes smk_preserve and relabels the copied-up inode.
Furthermore, ovl_copy_up_file() opens the lower file internally via
ovl_path_open(..., O_LARGEFILE | O_RDONLY), which reaches smack_file_open()
and records smk_preserve on current even though the user only truncated or
opened the file for writing.

>  	if (rc == 0)
>  		if (xattr_dupval(xattrs, xattr_count,
>  			    XATTR_SMACK_SUFFIX,
> @@ -2077,6 +2102,39 @@ static int smack_file_open(struct file *file)

[ ... ]

> +	if (!list_empty(&smk_preserve_wl) &&
> +	    (file->f_flags & O_ACCMODE) == O_RDONLY &&
> +	    S_ISREG(inode->i_mode)) {
> +		struct smack_known *skp = smk_of_inode(inode);
> +
> +		if (smk_preserve_allowed(skp))
> +			smack_task(current)->smk_preserve = skp;
> +	}
> +#endif

[Severity: Medium]
If a task with a pending smk_preserve creates a new regular file using
open(path, O_RDONLY | O_CREAT, mode) (or open(path, O_CREAT | O_EXCL, mode),
since O_RDONLY is 0), lookup_open() sets FMODE_CREATED and invokes
smack_inode_init_security(), which labels the new inode with skp and clears
smack_task(current)->smk_preserve = NULL.

Later in that same open() syscall, do_open() -> vfs_open() -> do_dentry_open()
calls security_file_open() -> smack_file_open() on the newly created file.
Because (file->f_flags & O_ACCMODE) == O_RDONLY is true and FMODE_CREATED is
not excluded here, won't smack_file_open() see the newly inherited skp on
inode and immediately set smack_task(current)->smk_preserve = skp again,
defeating the one-shot consumption?

> diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
> index a72bc7fabea99..7a6dc3e2eb812 100644
> --- a/security/smack/smackfs.c
> +++ b/security/smack/smackfs.c

[ ... ]

> @@ -2777,6 +2780,97 @@ static const struct file_operations smk_ptrace_ops = {

[ ... ]

> +static ssize_t smk_write_preserve_wl(struct file *file, const char __user *buf,
> +				     size_t count, loff_t *ppos)
> +{
> +	struct smack_known *skp;
> +	char *data, *label;
> +	bool on = true;
> +
> +	if (!smack_privileged(CAP_MAC_ADMIN))
> +		return -EPERM;
> +
> +	data = memdup_user_nul(buf, count);
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	label = strim(data);
> +	if (!label[0]) {
> +		kfree(data);
> +		return -EINVAL;
> +	}
> +
> +	/* A leading '-' removes the label (same convention as revoke-subject). */
> +	if (label[0] == '-') {
> +		on = false;
> +		label = strim(label + 1);
> +		if (!label[0]) {
> +			kfree(data);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	skp = smk_find_entry(label);
> +	if (!skp) {
> +		kfree(data);
> +		return -ENOENT;
> +	}

[Severity: Medium]
When adding a label to /smack/preserve-whitelist (for example at boot before a
removable filesystem with smackfsdef= is mounted or before policy rules
referencing that label are loaded), smk_find_entry(label) returns NULL and
smk_write_preserve_wl() fails with -ENOENT if the label has not already been
imported into smack_known_hash.

Other smackfs label configuration writers such as smk_write_onlycap(),
smk_write_ambient(), smk_write_unconfined(), smk_write_syslog(), and
smk_write_relabel_self() check *ppos != 0, bound count (to SMK_LONGLABEL /
PAGE_SIZE), and call smk_import_entry(label, 0) when adding a label so valid
labels are imported on demand. Should smk_write_preserve_wl() also bound count
and use smk_import_entry() when on is true?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/684C81A1C8289709+20261028161521.925644-1-tangpengke@yhkylin.cn?part=1

      reply	other threads:[~2026-10-28 16:37 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28 16:15 [PATCH v3] smack: preserve low-integrity labels across file copy via whitelist Tang Pengke
2026-10-28 16:37 ` sashiko-bot [this message]

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-188170@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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