From: Casey Schaufler <casey@schaufler-ca.com>
To: Tang Peter <Peter_Towne@hotmail.com>
Cc: "linux-security-module@vger.kernel.org"
<linux-security-module@vger.kernel.org>,
Casey Schaufler <casey@schaufler-ca.com>
Subject: Re: [RFC PATCH 0/3] smack: add file label preserve mechanism
Date: Tue, 18 Aug 2026 09:11:25 -0700 [thread overview]
Message-ID: <d0cf31df-05e6-4d77-b6a0-ce6c2c7c009d@schaufler-ca.com> (raw)
In-Reply-To: <TY0PR06MB49571F55B361203419222EDA81A62@TY0PR06MB4957.apcprd06.prod.outlook.com>
On 8/18/2026 1:05 AM, Tang Peter wrote:
> Subject: [RFC PATCH 0/3] smack: add file label preserve mechanism
>
> Hi Casey,
>
> I'm working on an embedded Linux device (MIPS, BusyBox) that uses SMACK
> for mandatory access control, and I ran into a problem that I think is
> worth your opinion on.
>
> Problem
> -------
> When a user copies a file with BusyBox cp, the destination file inherits
> the *process* label rather than the *source* file's label.
This is the correct and expected behavior. A process that creates
a new file has the opportunity to put whatever data it desires in it,
so the new file has to have the process label, regardless of any labeling
of the source information.
Cases like this are why Smack has transmuting labels. Without more information
about the specifics of your directory hierarchy and labeling strategy it's
hard to offer specific advice, but if what you have is a limited set of files
that require label preservation you should be able to use that mechanism.
> On desktop or
> server systems this is solved in userspace -- GNU cp has
> --preserve=context, and SELinux has restorecond. But on an embedded
> device with BusyBox, neither is available, and the result is that the
> source file's label (e.g. an untrusted "USB" label) is silently lost
> after the copy, which breaks both access control and audit tracing.
>
> Proposed mechanism
> ------------------
> An opt-in "preserve" feature controlled via /sys/fs/smackfs/preserve
> (default off):
>
> 1. smack_file_open(): when a regular file is opened O_RDONLY,
> record its label into task_smack::smk_preserve. Shared library
> loads under /lib/ and /usr/lib/ are skipped (path-prefix filter)
> so ld.so does not pollute the preserve slot.
>
> 2. smack_inode_init_security(): if smk_preserve is set, use it as
> the new inode's label and clear it. TRANSMUTE still takes
> priority, so existing transmuting-directory behaviour is unchanged.
>
> 3. The slot is one-shot: cleared on use, on fork (cred_prepare),
> and on exec (bprm_creds_for_exec), so it only applies to a
> "read then create" sequence within a single process.
>
> This makes `cp a b`, `cat a > b`, and similar BusyBox workflows keep
> the source label without any userspace changes.
I have an evil program "ecp", which uses this mechanism. ecp opens a
file labeled "_", and creates a new executable file /bin/xyzzy. It then
opens a file labeled "somethingelse", which contains a malicious program.
/bin/xyzzy is filled with the malicious program. Now, processes that
have no access to somethingelse are exposed to the malicious program.
You could restrict smk_preserve to programs with CAP_MAC_ADMIN, but
such a process could relabel the file directly, rendering the mechanism
unnecessary.
>
> Questions
> ---------
> - Does this approach make sense for SMACK, or is there an existing /
> preferred way to solve this that I've missed?
> - Any concerns about the one-shot slot semantics, or the /lib/ filter?
>
> Thanks,
> Tang Pengke
>
> ---
> security/smack/smack.h | 2 ++
> security/smack/smack_lsm.c | 45
> +++++++++++++++++++++++++++++++++++++++++----
> security/smack/smackfs.c | 50
> ++++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 93 insertions(+), 4 deletions(-)
>
>
> From: Tang Pengke <peter_towne@hotmail.com>
> Subject: [RFC PATCH 1/3] smack: add smk_preserve field to task_smack
>
> Add a smk_preserve pointer to struct task_smack for recording
> the label of the last file opened read-only. Also declare the
> global smack_preserve toggle variable.
>
> Signed-off-by: Tang Pengke <peter_towne@hotmail.com>
> ---
> security/smack/smack.h | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/security/smack/smack.h b/security/smack/smack.h
> index 8ad30955e15..c6eea57cf52 100644
> --- a/security/smack/smack.h
> +++ b/security/smack/smack.h
> @@ -124,6 +124,7 @@ struct task_smack {
> struct list_head smk_rules; /* per task access rules */
> struct mutex smk_rules_lock; /* lock for the rules */
> struct list_head smk_relabel; /* transit allowed labels */
> + struct smack_known *smk_preserve; /* label to preserve
> on create */
> };
>
> #define SMK_INODE_INSTANT 0x01 /* inode is instantiated */
> @@ -325,6 +326,7 @@ extern struct smack_known *smack_unconfined;
> #endif
> extern int smack_ptrace_rule;
> +extern int smack_preserve;
> extern struct lsm_blob_sizes smack_blob_sizes;
>
> extern struct smack_known smack_known_floor;
>
>
> From: Tang Pengke <peter_towne@hotmail.com>
> Subject: [RFC PATCH 2/3] smack: implement file label preserve mechanism
>
> When smack_preserve is enabled, opening a regular file read-only
> records its label in task_smack::smk_preserve. On the next file
> creation (smack_inode_init_security), that label is applied to the
> new inode -- unless TRANSMUTE takes priority.
>
> The preserve is one-shot:
> - Consumed immediately on new inode creation
> - Cleared on fork (smack_cred_prepare)
> - Cleared on exec (smack_bprm_creds_for_exec)
>
> Shared library loads under /lib/ and /usr/lib/ are explicitly
> excluded from recording, so ld.so does not pollute the preserve
> label during exec. Non-regular files (directories, devices) are
> also skipped.
>
> This enables tools like cp and 'cat > file' to create files that
> retain the source file's label, without requiring any userspace
> changes. The feature is disabled by default (preserve=0).
>
> Signed-off-by: Tang Pengke <peter_towne@hotmail.com>
> ---
> security/smack/smack_lsm.c | 45
> +++++++++++++++++++++++++++++++++++++++++----
> 1 file changed, 41 insertions(+), 4 deletions(-)
>
> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index 11f238e94..951c79d9e 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -935,6 +935,7 @@ static int smack_bprm_creds_for_exec(struct
> linux_binprm *bprm)
> return -EPERM;
>
> bsp->smk_task = isp->smk_task;
> + bsp->smk_preserve = NULL;
> bprm->per_clear |= PER_CLEAR_ON_SETID;
>
> /* Decide if this is a secure exec. */
> @@ -1066,6 +1067,15 @@ static int smack_inode_init_security(struct
> inode *inode, struct inode *dir,
> }
> }
>
> + /*
> + * If a preserve label was recorded on file open, use it
> + * for the new inode (unless TRANSMUTE already applied above).
> + */
> + if (tsp->smk_preserve != NULL) {
> + issp->smk_inode = tsp->smk_preserve;
> + tsp->smk_preserve = NULL;
> + }
> +
> if (rc == 0)
> if (xattr_dupval(xattrs, xattr_count,
> XATTR_SMACK_SUFFIX,
> @@ -2064,6 +2074,27 @@ static int smack_file_open(struct file *file)
> struct smk_audit_info ad;
> int rc;
>
> + if (smack_preserve && (file->f_flags & O_ACCMODE) == O_RDONLY) {
> + char *buf;
> + const char *path;
> +
> + if (!S_ISREG(inode->i_mode))
> + goto skip_preserve;
> +
> + buf = kmalloc(PATH_MAX, GFP_KERNEL);
> + if (!buf)
> + goto skip_preserve;
> +
> + path = dentry_path_raw(file->f_path.dentry, buf, PATH_MAX);
> + if (!IS_ERR(path) &&
> + strncmp(path, "/lib/", 5) != 0 &&
> + strncmp(path, "/usr/lib/", 9) != 0)
> + tsp->smk_preserve = smk_of_inode(inode);
> +
> + kfree(buf);
> + }
> +skip_preserve:
> +
> smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH);
> smk_ad_setfield_u_fs_path(&ad, file->f_path);
> rc = smk_tskacc(tsp, smk_of_inode(inode), MAY_READ, &ad);
> @@ -2129,6 +2160,7 @@ static int smack_cred_prepare(struct cred *new,
> const struct cred *old,
> int rc;
>
> init_task_smack(new_tsp, old_tsp->smk_task, old_tsp->smk_task);
> + new_tsp->smk_preserve = NULL;
>
> rc = smk_copy_rules(&new_tsp->smk_rules, &old_tsp->smk_rules, gfp);
> if (rc != 0)
>
>
> From: Tang Pengke <peter_towne@hotmail.com>
> Subject: [RFC PATCH 3/3] smack: add /sys/fs/smackfs/preserve interface
>
> Expose the smack_preserve toggle via smackfs. Reading returns '0'
> or '1'. Writing '0' or '1' toggles the feature (requires
> CAP_MAC_ADMIN). Default is disabled (0) so existing systems are
> unaffected.
>
> Signed-off-by: Tang Pengke <peter_towne@hotmail.com>
> ---
> security/smack/smackfs.c | 50
> ++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 50 insertions(+)
>
> diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
> index a3a55a25360..8379ef73cf2 100644
> --- a/security/smack/smackfs.c
> +++ b/security/smack/smackfs.c
> @@ -62,6 +62,7 @@ enum smk_inos {
> SMK_NET6ADDR = 23, /* single label IPv6 hosts */
> #endif /* CONFIG_IPV6 */
> SMK_RELABEL_SELF = 24, /* relabel possible without CAP_MAC_ADMIN */
> + SMK_PRESERVE = 25, /* label preserve on copy */
> };
>
> /*
> @@ -2047,6 +2048,53 @@ static ssize_t smk_write_onlycap(struct file
> *file, const char __user *buf,
> return rc;
> }
>
> +int smack_preserve;
> +
> +static ssize_t smk_read_preserve(struct file *filp, char __user *buf,
> + size_t cn, loff_t *ppos)
> +{
> + char val[2];
> +
> + if (*ppos != 0)
> + return 0;
> +
> + val[0] = smack_preserve ? '1' : '0';
> + val[1] = '\n';
> +
> + return simple_read_from_buffer(buf, cn, ppos, val, 2);
> +}
> +
> +static ssize_t smk_write_preserve(struct file *file, const char
> __user *buf,
> + size_t count, loff_t *ppos)
> +{
> + char data;
> +
> + if (!smack_privileged(CAP_MAC_ADMIN))
> + return -EPERM;
> +
> + if (*ppos != 0)
> + return -EINVAL;
> +
> + if (copy_from_user(&data, buf, 1) != 0)
> + return -EFAULT;
> +
> + if (data == '0')
> + smack_preserve = 0;
> + else if (data == '1')
> + smack_preserve = 1;
> + else
> + return -EINVAL;
> +
> + return count;
> +}
> +
> +static const struct file_operations smk_preserve_ops = {
> + .read = smk_read_preserve,
> + .write = smk_write_preserve,
> + .llseek = default_llseek,
> +};
> +
> static const struct file_operations smk_onlycap_ops = {
> .open = smk_open_onlycap,
> .read = seq_read,
> @@ -2933,6 +2981,8 @@ static int smk_fill_super(struct super_block
> *sb, struct fs_context *fc)
> [SMK_RELABEL_SELF] = {
> "relabel-self", &smk_relabel_self_ops,
> S_IRUGO|S_IWUGO},
> + [SMK_PRESERVE] = {
> + "preserve", &smk_preserve_ops, S_IRUGO|S_IWUSR},
> /* last one */
> {""}
> };
next parent reply other threads:[~2026-08-18 16:21 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <TY0PR06MB49571F55B361203419222EDA81A62@TY0PR06MB4957.apcprd06.prod.outlook.com>
2026-08-18 16:11 ` Casey Schaufler [this message]
[not found] ` <TY0PR06MB495733D0FB097FB83F8E707981A42@TY0PR06MB4957.apcprd06.prod.outlook.com>
2026-08-20 20:11 ` 回复: [RFC PATCH 0/3] smack: add file label preserve mechanism Casey Schaufler
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=d0cf31df-05e6-4d77-b6a0-ce6c2c7c009d@schaufler-ca.com \
--to=casey@schaufler-ca.com \
--cc=Peter_Towne@hotmail.com \
--cc=linux-security-module@vger.kernel.org \
/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