Commit 204a920f authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'Smack-for-6.19' of https://github.com/cschaufler/smack-next

Pull smack updates from Casey Schaufler:

 - fix several cases where labels were treated inconsistently when
   imported from user space

 - clean up the assignment of extended attributes

 - documentation improvements

* tag 'Smack-for-6.19' of https://github.com/cschaufler/smack-next:
  Smack: function parameter 'gfp' not described
  smack: fix kernel-doc warnings for smk_import_valid_label()
  smack: fix bug: setting task label silently ignores input garbage
  smack: fix bug: unprivileged task can create labels
  smack: fix bug: invalid label of unix socket file
  smack: always "instantiate" inode in smack_inode_init_security()
  smack: deduplicate xattr setting in smack_inode_init_security()
  smack: fix bug: SMACK64TRANSMUTE set on non-directory
  smack: deduplicate "does access rule request transmutation"
parents 0eae3283 29c701f9
Loading
Loading
Loading
Loading
+13 −3
Original line number Diff line number Diff line
@@ -601,10 +601,15 @@ specification.
Task Attribute
~~~~~~~~~~~~~~

The Smack label of a process can be read from /proc/<pid>/attr/current. A
process can read its own Smack label from /proc/self/attr/current. A
The Smack label of a process can be read from ``/proc/<pid>/attr/current``. A
process can read its own Smack label from ``/proc/self/attr/current``. A
privileged process can change its own Smack label by writing to
/proc/self/attr/current but not the label of another process.
``/proc/self/attr/current`` but not the label of another process.

Format of writing is : only the label or the label followed by one of the
3 trailers: ``\n`` (by common agreement for ``/proc/...`` interfaces),
``\0`` (because some applications incorrectly include it),
``\n\0`` (because we think some applications may incorrectly include it).

File Attribute
~~~~~~~~~~~~~~
@@ -696,6 +701,11 @@ sockets.
	A privileged program may set this to match the label of another
	task with which it hopes to communicate.

UNIX domain socket (UDS) with a BSD address functions both as a file in a
filesystem and as a socket. As a file, it carries the SMACK64 attribute. This
attribute is not involved in Smack security enforcement and is immutably
assigned the label "*".

Smack Netlabel Exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~

+3 −0
Original line number Diff line number Diff line
@@ -300,9 +300,12 @@ int smk_tskacc(struct task_smack *, struct smack_known *,
int smk_curacc(struct smack_known *, u32, struct smk_audit_info *);
int smack_str_from_perm(char *string, int access);
struct smack_known *smack_from_secid(const u32);
int smk_parse_label_len(const char *string, int len);
char *smk_parse_smack(const char *string, int len);
int smk_netlbl_mls(int, char *, struct netlbl_lsm_secattr *, int);
struct smack_known *smk_import_entry(const char *, int);
struct smack_known *smk_import_valid_label(const char *label, int label_len,
					   gfp_t gfp);
void smk_insert_entry(struct smack_known *skp);
struct smack_known *smk_find_entry(const char *);
bool smack_privileged(int cap);
+74 −22
Original line number Diff line number Diff line
@@ -443,19 +443,19 @@ struct smack_known *smk_find_entry(const char *string)
}

/**
 * smk_parse_smack - parse smack label from a text string
 * @string: a text string that might contain a Smack label
 * @len: the maximum size, or zero if it is NULL terminated.
 * smk_parse_label_len - calculate the length of the starting segment
 *                       in the string that constitutes a valid smack label
 * @string: a text string that might contain a Smack label at the beginning
 * @len: the maximum size to look into, may be zero if string is null-terminated
 *
 * Returns a pointer to the clean label or an error code.
 * Returns the length of the segment (0 < L < SMK_LONGLABEL) or an error code.
 */
char *smk_parse_smack(const char *string, int len)
int smk_parse_label_len(const char *string, int len)
{
	char *smack;
	int i;

	if (len <= 0)
		len = strlen(string) + 1;
	if (len <= 0 || len > SMK_LONGLABEL)
		len = SMK_LONGLABEL;

	/*
	 * Reserve a leading '-' as an indicator that
@@ -463,7 +463,7 @@ char *smk_parse_smack(const char *string, int len)
	 * including /smack/cipso and /smack/cipso2
	 */
	if (string[0] == '-')
		return ERR_PTR(-EINVAL);
		return -EINVAL;

	for (i = 0; i < len; i++)
		if (string[i] > '~' || string[i] <= ' ' || string[i] == '/' ||
@@ -471,6 +471,25 @@ char *smk_parse_smack(const char *string, int len)
			break;

	if (i == 0 || i >= SMK_LONGLABEL)
		return -EINVAL;

	return i;
}

/**
 * smk_parse_smack - copy the starting segment in the string
 *                   that constitutes a valid smack label
 * @string: a text string that might contain a Smack label at the beginning
 * @len: the maximum size to look into, may be zero if string is null-terminated
 *
 * Returns a pointer to the copy of the label or an error code.
 */
char *smk_parse_smack(const char *string, int len)
{
	char *smack;
	int i = smk_parse_label_len(string, len);

	if (i < 0)
		return ERR_PTR(-EINVAL);

	smack = kstrndup(string, i, GFP_NOFS);
@@ -554,31 +573,26 @@ int smack_populate_secattr(struct smack_known *skp)
}

/**
 * smk_import_entry - import a label, return the list entry
 * @string: a text string that might be a Smack label
 * @len: the maximum size, or zero if it is NULL terminated.
 * smk_import_valid_allocated_label - import a label, return the list entry
 * @smack: a text string that is a valid Smack label and may be kfree()ed.
 *         It is consumed: either becomes a part of the entry or kfree'ed.
 * @gfp: Allocation type
 *
 * Returns a pointer to the entry in the label list that
 * matches the passed string, adding it if necessary,
 * or an error code.
 * Returns: see description of smk_import_entry()
 */
struct smack_known *smk_import_entry(const char *string, int len)
static struct smack_known *
smk_import_allocated_label(char *smack, gfp_t gfp)
{
	struct smack_known *skp;
	char *smack;
	int rc;

	smack = smk_parse_smack(string, len);
	if (IS_ERR(smack))
		return ERR_CAST(smack);

	mutex_lock(&smack_known_lock);

	skp = smk_find_entry(smack);
	if (skp != NULL)
		goto freeout;

	skp = kzalloc(sizeof(*skp), GFP_NOFS);
	skp = kzalloc(sizeof(*skp), gfp);
	if (skp == NULL) {
		skp = ERR_PTR(-ENOMEM);
		goto freeout;
@@ -608,6 +622,44 @@ struct smack_known *smk_import_entry(const char *string, int len)
	return skp;
}

/**
 * smk_import_entry - import a label, return the list entry
 * @string: a text string that might contain a Smack label at the beginning
 * @len: the maximum size to look into, may be zero if string is null-terminated
 *
 * Returns a pointer to the entry in the label list that
 * matches the passed string, adding it if necessary,
 * or an error code.
 */
struct smack_known *smk_import_entry(const char *string, int len)
{
	char *smack = smk_parse_smack(string, len);

	if (IS_ERR(smack))
		return ERR_CAST(smack);

	return smk_import_allocated_label(smack, GFP_NOFS);
}

/**
 * smk_import_valid_label - import a label, return the list entry
 * @label: a text string that is a valid Smack label, not null-terminated
 * @label_len: the length of the text string in the @label
 * @gfp: the GFP mask used for allocating memory for the @label text string copy
 *
 * Return: see description of smk_import_entry()
 */
struct smack_known *
smk_import_valid_label(const char *label, int label_len, gfp_t gfp)
{
	char *smack = kstrndup(label, label_len, gfp);

	if  (!smack)
		return ERR_PTR(-ENOMEM);

	return smk_import_allocated_label(smack, gfp);
}

/**
 * smack_from_secid - find the Smack label associated with a secid
 * @secid: an integer that might be associated with a Smack label
+185 −94
Original line number Diff line number Diff line
@@ -962,6 +962,42 @@ static int smack_inode_alloc_security(struct inode *inode)
	return 0;
}

/**
 * smk_rule_transmutes - does access rule for (subject,object) contain 't'?
 * @subject: a pointer to the subject's Smack label entry
 * @object: a pointer to the object's Smack label entry
 */
static bool
smk_rule_transmutes(struct smack_known *subject,
	      const struct smack_known *object)
{
	int may;

	rcu_read_lock();
	may = smk_access_entry(subject->smk_known, object->smk_known,
			       &subject->smk_rules);
	rcu_read_unlock();
	return (may > 0) && (may & MAY_TRANSMUTE);
}

static int
xattr_dupval(struct xattr *xattrs, int *xattr_count,
	     const char *name, const void *value, unsigned int vallen)
{
	struct xattr * const xattr = lsm_get_xattr_slot(xattrs, xattr_count);

	if (!xattr)
		return 0;

	xattr->value = kmemdup(value, vallen, GFP_NOFS);
	if (!xattr->value)
		return -ENOMEM;

	xattr->value_len = vallen;
	xattr->name = name;
	return 0;
}

/**
 * smack_inode_init_security - copy out the smack from an inode
 * @inode: the newly created inode
@@ -977,23 +1013,30 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir,
				     struct xattr *xattrs, int *xattr_count)
{
	struct task_smack *tsp = smack_cred(current_cred());
	struct inode_smack *issp = smack_inode(inode);
	struct smack_known *skp = smk_of_task(tsp);
	struct smack_known *isp = smk_of_inode(inode);
	struct inode_smack * const issp = smack_inode(inode);
	struct smack_known *dsp = smk_of_inode(dir);
	struct xattr *xattr = lsm_get_xattr_slot(xattrs, xattr_count);
	int may;
	int rc = 0;
	int transflag = 0;
	bool trans_cred;
	bool trans_rule;

	/*
	 * UNIX domain sockets use lower level socket data. Let
	 * UDS inode have fixed * label to keep smack_inode_permission() calm
	 * when called from unix_find_bsd()
	 */
	if (S_ISSOCK(inode->i_mode)) {
		/* forced label, no need to save to xattrs */
		issp->smk_inode = &smack_known_star;
		goto instant_inode;
	}
	/*
	 * If equal, transmuting already occurred in
	 * smack_dentry_create_files_as(). No need to check again.
	 */
	if (tsp->smk_task != tsp->smk_transmuted) {
		rcu_read_lock();
		may = smk_access_entry(skp->smk_known, dsp->smk_known,
				       &skp->smk_rules);
		rcu_read_unlock();
	}
	trans_cred = (tsp->smk_task == tsp->smk_transmuted);
	if (!trans_cred)
		trans_rule = smk_rule_transmutes(smk_of_task(tsp), dsp);

	/*
	 * In addition to having smk_task equal to smk_transmuted,
@@ -1001,47 +1044,38 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir,
	 * requests transmutation then by all means transmute.
	 * Mark the inode as changed.
	 */
	if ((tsp->smk_task == tsp->smk_transmuted) ||
	    (may > 0 && ((may & MAY_TRANSMUTE) != 0) &&
	     smk_inode_transmutable(dir))) {
		struct xattr *xattr_transmute;

	if (trans_cred || (trans_rule && smk_inode_transmutable(dir))) {
		/*
		 * The caller of smack_dentry_create_files_as()
		 * should have overridden the current cred, so the
		 * inode label was already set correctly in
		 * smack_inode_alloc_security().
		 */
		if (tsp->smk_task != tsp->smk_transmuted)
			isp = issp->smk_inode = dsp;

		issp->smk_flags |= SMK_INODE_TRANSMUTE;
		xattr_transmute = lsm_get_xattr_slot(xattrs,
						     xattr_count);
		if (xattr_transmute) {
			xattr_transmute->value = kmemdup(TRANS_TRUE,
							 TRANS_TRUE_SIZE,
							 GFP_NOFS);
			if (!xattr_transmute->value)
				return -ENOMEM;

			xattr_transmute->value_len = TRANS_TRUE_SIZE;
			xattr_transmute->name = XATTR_SMACK_TRANSMUTE;
		}
	}

	issp->smk_flags |= SMK_INODE_INSTANT;
		if (!trans_cred)
			issp->smk_inode = dsp;

	if (xattr) {
		xattr->value = kstrdup(isp->smk_known, GFP_NOFS);
		if (!xattr->value)
			return -ENOMEM;
		if (S_ISDIR(inode->i_mode)) {
			transflag = SMK_INODE_TRANSMUTE;

		xattr->value_len = strlen(isp->smk_known);
		xattr->name = XATTR_SMACK_SUFFIX;
			if (xattr_dupval(xattrs, xattr_count,
				XATTR_SMACK_TRANSMUTE,
				TRANS_TRUE,
				TRANS_TRUE_SIZE
			))
				rc = -ENOMEM;
		}
	}

	return 0;
	if (rc == 0)
		if (xattr_dupval(xattrs, xattr_count,
			    XATTR_SMACK_SUFFIX,
			    issp->smk_inode->smk_known,
		     strlen(issp->smk_inode->smk_known)
		))
			rc = -ENOMEM;
instant_inode:
	issp->smk_flags |= (SMK_INODE_INSTANT | transflag);
	return rc;
}

/**
@@ -1315,12 +1349,22 @@ static int smack_inode_setxattr(struct mnt_idmap *idmap,
	int check_import = 0;
	int check_star = 0;
	int rc = 0;
	umode_t const i_mode = d_backing_inode(dentry)->i_mode;

	/*
	 * Check label validity here so import won't fail in post_setxattr
	 */
	if (strcmp(name, XATTR_NAME_SMACK) == 0 ||
	    strcmp(name, XATTR_NAME_SMACKIPIN) == 0 ||
	if (strcmp(name, XATTR_NAME_SMACK) == 0) {
		/*
		 * UDS inode has fixed label
		 */
		if (S_ISSOCK(i_mode)) {
			rc = -EINVAL;
		} else {
			check_priv = 1;
			check_import = 1;
		}
	} else if (strcmp(name, XATTR_NAME_SMACKIPIN) == 0 ||
		   strcmp(name, XATTR_NAME_SMACKIPOUT) == 0) {
		check_priv = 1;
		check_import = 1;
@@ -1331,7 +1375,7 @@ static int smack_inode_setxattr(struct mnt_idmap *idmap,
		check_star = 1;
	} else if (strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) {
		check_priv = 1;
		if (!S_ISDIR(d_backing_inode(dentry)->i_mode) ||
		if (!S_ISDIR(i_mode) ||
		    size != TRANS_TRUE_SIZE ||
		    strncmp(value, TRANS_TRUE, TRANS_TRUE_SIZE) != 0)
			rc = -EINVAL;
@@ -1462,12 +1506,15 @@ static int smack_inode_removexattr(struct mnt_idmap *idmap,
	 * Don't do anything special for these.
	 *	XATTR_NAME_SMACKIPIN
	 *	XATTR_NAME_SMACKIPOUT
	 *	XATTR_NAME_SMACK if S_ISSOCK (UDS inode has fixed label)
	 */
	if (strcmp(name, XATTR_NAME_SMACK) == 0) {
		if (!S_ISSOCK(d_backing_inode(dentry)->i_mode)) {
			struct super_block *sbp = dentry->d_sb;
			struct superblock_smack *sbsp = smack_superblock(sbp);

			isp->smk_inode = sbsp->smk_default;
		}
	} else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0)
		isp->smk_task = NULL;
	else if (strcmp(name, XATTR_NAME_SMACKMMAP) == 0)
@@ -3585,7 +3632,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode)
		 */

		/*
		 * UNIX domain sockets use lower level socket data.
		 * UDS inode has fixed label (*)
		 */
		if (S_ISSOCK(inode->i_mode)) {
			final = &smack_known_star;
@@ -3663,7 +3710,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode)
 * @attr: which attribute to fetch
 * @ctx: buffer to receive the result
 * @size: available size in, actual size out
 * @flags: unused
 * @flags: reserved, currently zero
 *
 * Fill the passed user space @ctx with the details of the requested
 * attribute.
@@ -3724,47 +3771,55 @@ static int smack_getprocattr(struct task_struct *p, const char *name, char **val
 * Sets the Smack value of the task. Only setting self
 * is permitted and only with privilege
 *
 * Returns the length of the smack label or an error code
 * Returns zero on success or an error code
 */
static int do_setattr(u64 attr, void *value, size_t size)
static int do_setattr(unsigned int attr, void *value, size_t size)
{
	struct task_smack *tsp = smack_cred(current_cred());
	struct cred *new;
	struct smack_known *skp;
	struct smack_known_list_elem *sklep;
	int rc;

	if (!smack_privileged(CAP_MAC_ADMIN) && list_empty(&tsp->smk_relabel))
		return -EPERM;
	int label_len;

	/*
	 * let unprivileged user validate input, check permissions later
	 */
	if (value == NULL || size == 0 || size >= SMK_LONGLABEL)
		return -EINVAL;

	if (attr != LSM_ATTR_CURRENT)
		return -EOPNOTSUPP;

	skp = smk_import_entry(value, size);
	if (IS_ERR(skp))
		return PTR_ERR(skp);
	label_len = smk_parse_label_len(value, size);
	if (label_len < 0 || label_len != size)
		return -EINVAL;

	/*
	 * No process is ever allowed the web ("@") label
	 * and the star ("*") label.
	 */
	if (skp == &smack_known_web || skp == &smack_known_star)
		return -EINVAL;
	if (label_len == 1 /* '@', '*' */) {
		const char c = *(const char *)value;

		if (c == *smack_known_web.smk_known ||
		    c == *smack_known_star.smk_known)
			return -EPERM;
	}

	if (!smack_privileged(CAP_MAC_ADMIN)) {
		rc = -EPERM;
		list_for_each_entry(sklep, &tsp->smk_relabel, list)
			if (sklep->smk_label == skp) {
				rc = 0;
				break;
		const struct smack_known_list_elem *sklep;
		list_for_each_entry(sklep, &tsp->smk_relabel, list) {
			const char *cp = sklep->smk_label->smk_known;

			if (strlen(cp) == label_len &&
			    strncmp(cp, value, label_len) == 0)
				goto in_relabel;
		}
		if (rc)
			return rc;
		return -EPERM;
in_relabel:
		;
	}

	skp = smk_import_valid_label(value, label_len, GFP_KERNEL);
	if (IS_ERR(skp))
		return PTR_ERR(skp);

	new = prepare_creds();
	if (new == NULL)
		return -ENOMEM;
@@ -3777,7 +3832,7 @@ static int do_setattr(u64 attr, void *value, size_t size)
	smk_destroy_label_list(&tsp->smk_relabel);

	commit_creds(new);
	return size;
	return 0;
}

/**
@@ -3785,7 +3840,7 @@ static int do_setattr(u64 attr, void *value, size_t size)
 * @attr: which attribute to set
 * @ctx: buffer containing the data
 * @size: size of @ctx
 * @flags: unused
 * @flags: reserved, must be zero
 *
 * Fill the passed user space @ctx with the details of the requested
 * attribute.
@@ -3795,12 +3850,26 @@ static int do_setattr(u64 attr, void *value, size_t size)
static int smack_setselfattr(unsigned int attr, struct lsm_ctx *ctx,
			     u32 size, u32 flags)
{
	int rc;
	if (attr != LSM_ATTR_CURRENT)
		return -EOPNOTSUPP;

	rc = do_setattr(attr, ctx->ctx, ctx->ctx_len);
	if (rc > 0)
		return 0;
	return rc;
	if (ctx->flags)
		return -EINVAL;
	/*
	 * string must have \0 terminator, included in ctx->ctx
	 * (see description of struct lsm_ctx)
	 */
	if (ctx->ctx_len == 0)
		return -EINVAL;

	if (ctx->ctx[ctx->ctx_len - 1] != '\0')
		return -EINVAL;
	/*
	 * other do_setattr() caller, smack_setprocattr(),
	 * does not count \0 into size, so
	 * decreasing length by 1 to accommodate the divergence.
	 */
	return do_setattr(attr, ctx->ctx, ctx->ctx_len - 1);
}

/**
@@ -3812,15 +3881,39 @@ static int smack_setselfattr(unsigned int attr, struct lsm_ctx *ctx,
 * Sets the Smack value of the task. Only setting self
 * is permitted and only with privilege
 *
 * Returns the length of the smack label or an error code
 * Returns the size of the input value or an error code
 */
static int smack_setprocattr(const char *name, void *value, size_t size)
{
	int attr = lsm_name_to_attr(name);
	size_t realsize = size;
	unsigned int attr = lsm_name_to_attr(name);

	if (attr != LSM_ATTR_UNDEF)
		return do_setattr(attr, value, size);
	return -EINVAL;
	switch (attr) {
	case LSM_ATTR_UNDEF:   return -EINVAL;
	default:               return -EOPNOTSUPP;
	case LSM_ATTR_CURRENT:
		;
	}

	/*
	 * The value for the "current" attribute is the label
	 * followed by one of the 4 trailers: none, \0, \n, \n\0
	 *
	 * I.e. following inputs are accepted as 3-characters long label "foo":
	 *
	 *   "foo"     (3 characters)
	 *   "foo\0"   (4 characters)
	 *   "foo\n"   (4 characters)
	 *   "foo\n\0" (5 characters)
	 */

	if (realsize && (((const char *)value)[realsize - 1] == '\0'))
		--realsize;

	if (realsize && (((const char *)value)[realsize - 1] == '\n'))
		--realsize;

	return do_setattr(attr, value, realsize) ? : size;
}

/**
@@ -4850,6 +4943,11 @@ static int smack_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid)

static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen)
{
	/*
	 * UDS inode has fixed label. Ignore nfs label.
	 */
	if (S_ISSOCK(inode->i_mode))
		return 0;
	return smack_inode_setsecurity(inode, XATTR_SMACK_SUFFIX, ctx,
				       ctxlen, 0);
}
@@ -4915,7 +5013,6 @@ static int smack_dentry_create_files_as(struct dentry *dentry, int mode,
	struct task_smack *otsp = smack_cred(old);
	struct task_smack *ntsp = smack_cred(new);
	struct inode_smack *isp;
	int may;

	/*
	 * Use the process credential unless all of
@@ -4929,18 +5026,12 @@ static int smack_dentry_create_files_as(struct dentry *dentry, int mode,
	isp = smack_inode(d_inode(dentry->d_parent));

	if (isp->smk_flags & SMK_INODE_TRANSMUTE) {
		rcu_read_lock();
		may = smk_access_entry(otsp->smk_task->smk_known,
				       isp->smk_inode->smk_known,
				       &otsp->smk_task->smk_rules);
		rcu_read_unlock();

		/*
		 * If the directory is transmuting and the rule
		 * providing access is transmuting use the containing
		 * directory label instead of the process label.
		 */
		if (may > 0 && (may & MAY_TRANSMUTE)) {
		if (smk_rule_transmutes(otsp->smk_task, isp->smk_inode)) {
			ntsp->smk_task = isp->smk_inode;
			ntsp->smk_transmuted = ntsp->smk_task;
		}