Commit b5628b81 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull selinux updates from Paul Moore:

 - Reduce the SELinux impact on path walks.

   Add a small directory access cache to the per-task SELinux state.
   This cache allows SELinux to cache the most recently used directory
   access decisions in order to avoid repeatedly querying the AVC on
   path walks where the majority of the directories have similar
   security contexts/labels.

   My performance measurements are crude, but prior to this patch the
   time spent in SELinux code on a 'make allmodconfig' run was 103% that
   of __d_lookup_rcu(), and with this patch the time spent in SELinux
   code dropped to 63% of __d_lookup_rcu(), a ~40% improvement.

   Additional improvments can be expected in the future, but those will
   require additional SELinux policy/toolchain support.

 - Add support for wildcards in genfscon policy statements.

   This patch allows for wildcards in the genfscon patch matching logic
   as opposed to the prefix matching that was used prior to this change.
   Adding wilcard support allows for more expressive and efficient path
   matching in the policy which is especially helpful for sysfs, and has
   resulted in a ~15% boot time reduction in Android.

   SELinux policies can opt into wilcard matching by using the
   "genfs_seclabel_wildcard" policy capability.

 - Unify the error/OOM handling of the SELinux network caches.

   A failure to allocate memory for the SELinux network caches isn't
   fatal as the object label can still be safely returned to the caller,
   it simply means that we cannot add the new data to the cache, at
   least temporarily. This patch corrects this behavior for the
   InfiniBand cache and does some minor cleanup.

 - Minor improvements around constification, 'likely' annotations, and
   removal of bogus comments.

* tag 'selinux-pr-20250527' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux:
  selinux: fix the kdoc header for task_avdcache_update
  selinux: remove a duplicated include
  selinux: reduce path walk overhead
  selinux: support wildcard match in genfscon
  selinux: drop copy-paste comment
  selinux: unify OOM handling in network hashtables
  selinux: add likely hints for fast paths
  selinux: contify network namespace pointer
  selinux: constify network address pointer
parents 1bc8c83a 05f1a939
Loading
Loading
Loading
Loading
+171 −54
Original line number Diff line number Diff line
@@ -213,8 +213,10 @@ static void cred_init_security(void)
{
	struct task_security_struct *tsec;

	/* NOTE: the lsm framework zeros out the buffer on allocation */

	tsec = selinux_cred(unrcu_pointer(current->real_cred));
	tsec->osid = tsec->sid = SECINITSID_KERNEL;
	tsec->osid = tsec->sid = tsec->avdcache.sid = SECINITSID_KERNEL;
}

/*
@@ -278,27 +280,21 @@ static int __inode_security_revalidate(struct inode *inode,
				       struct dentry *dentry,
				       bool may_sleep)
{
	struct inode_security_struct *isec = selinux_inode(inode);

	might_sleep_if(may_sleep);
	if (!selinux_initialized())
		return 0;

	/*
	 * The check of isec->initialized below is racy but
	 * inode_doinit_with_dentry() will recheck with
	 * isec->lock held.
	 */
	if (selinux_initialized() &&
	    data_race(isec->initialized != LABEL_INITIALIZED)) {
		if (!may_sleep)
	if (may_sleep)
		might_sleep();
	else
		return -ECHILD;

	/*
		 * Try reloading the inode security label.  This will fail if
		 * @opt_dentry is NULL and no dentry for this inode can be
		 * found; in that case, continue using the old label.
	 * Check to ensure that an inode's SELinux state is valid and try
	 * reloading the inode security label if necessary.  This will fail if
	 * @dentry is NULL and no dentry for this inode can be found; in that
	 * case, continue using the old label.
	 */
	inode_doinit_with_dentry(inode, dentry);
	}
	return 0;
}

@@ -307,41 +303,53 @@ static struct inode_security_struct *inode_security_novalidate(struct inode *ino
	return selinux_inode(inode);
}

static struct inode_security_struct *inode_security_rcu(struct inode *inode, bool rcu)
static inline struct inode_security_struct *inode_security_rcu(struct inode *inode,
							       bool rcu)
{
	int error;
	int rc;
	struct inode_security_struct *isec = selinux_inode(inode);

	error = __inode_security_revalidate(inode, NULL, !rcu);
	if (error)
		return ERR_PTR(error);
	return selinux_inode(inode);
	/* check below is racy, but revalidate will recheck with lock held */
	if (data_race(likely(isec->initialized == LABEL_INITIALIZED)))
		return isec;
	rc = __inode_security_revalidate(inode, NULL, !rcu);
	if (rc)
		return ERR_PTR(rc);
	return isec;
}

/*
 * Get the security label of an inode.
 */
static struct inode_security_struct *inode_security(struct inode *inode)
static inline struct inode_security_struct *inode_security(struct inode *inode)
{
	struct inode_security_struct *isec = selinux_inode(inode);

	/* check below is racy, but revalidate will recheck with lock held */
	if (data_race(likely(isec->initialized == LABEL_INITIALIZED)))
		return isec;
	__inode_security_revalidate(inode, NULL, true);
	return selinux_inode(inode);
	return isec;
}

static struct inode_security_struct *backing_inode_security_novalidate(struct dentry *dentry)
static inline struct inode_security_struct *backing_inode_security_novalidate(struct dentry *dentry)
{
	struct inode *inode = d_backing_inode(dentry);

	return selinux_inode(inode);
	return selinux_inode(d_backing_inode(dentry));
}

/*
 * Get the security label of a dentry's backing inode.
 */
static struct inode_security_struct *backing_inode_security(struct dentry *dentry)
static inline struct inode_security_struct *backing_inode_security(struct dentry *dentry)
{
	struct inode *inode = d_backing_inode(dentry);
	struct inode_security_struct *isec = selinux_inode(inode);

	/* check below is racy, but revalidate will recheck with lock held */
	if (data_race(likely(isec->initialized == LABEL_INITIALIZED)))
		return isec;
	__inode_security_revalidate(inode, dentry, true);
	return selinux_inode(inode);
	return isec;
}

static void inode_free_security(struct inode *inode)
@@ -1683,11 +1691,14 @@ static inline int dentry_has_perm(const struct cred *cred,
				  struct dentry *dentry,
				  u32 av)
{
	struct inode *inode = d_backing_inode(dentry);
	struct common_audit_data ad;
	struct inode *inode = d_backing_inode(dentry);
	struct inode_security_struct *isec = selinux_inode(inode);

	ad.type = LSM_AUDIT_DATA_DENTRY;
	ad.u.dentry = dentry;
	/* check below is racy, but revalidate will recheck with lock held */
	if (data_race(unlikely(isec->initialized != LABEL_INITIALIZED)))
		__inode_security_revalidate(inode, dentry, true);
	return inode_has_perm(cred, inode, av, &ad);
}
@@ -1699,11 +1710,14 @@ static inline int path_has_perm(const struct cred *cred,
				const struct path *path,
				u32 av)
{
	struct inode *inode = d_backing_inode(path->dentry);
	struct common_audit_data ad;
	struct inode *inode = d_backing_inode(path->dentry);
	struct inode_security_struct *isec = selinux_inode(inode);

	ad.type = LSM_AUDIT_DATA_PATH;
	ad.u.path = *path;
	/* check below is racy, but revalidate will recheck with lock held */
	if (data_race(unlikely(isec->initialized != LABEL_INITIALIZED)))
		__inode_security_revalidate(inode, path->dentry, true);
	return inode_has_perm(cred, inode, av, &ad);
}
@@ -3088,44 +3102,147 @@ static noinline int audit_inode_permission(struct inode *inode,
			    audited, denied, result, &ad);
}

static int selinux_inode_permission(struct inode *inode, int mask)
/**
 * task_avdcache_reset - Reset the task's AVD cache
 * @tsec: the task's security state
 *
 * Clear the task's AVD cache in @tsec and reset it to the current policy's
 * and task's info.
 */
static inline void task_avdcache_reset(struct task_security_struct *tsec)
{
	memset(&tsec->avdcache.dir, 0, sizeof(tsec->avdcache.dir));
	tsec->avdcache.sid = tsec->sid;
	tsec->avdcache.seqno = avc_policy_seqno();
	tsec->avdcache.dir_spot = TSEC_AVDC_DIR_SIZE - 1;
}

/**
 * task_avdcache_search - Search the task's AVD cache
 * @tsec: the task's security state
 * @isec: the inode to search for in the cache
 * @avdc: matching avd cache entry returned to the caller
 *
 * Search @tsec for a AVD cache entry that matches @isec and return it to the
 * caller via @avdc.  Returns 0 if a match is found, negative values otherwise.
 */
static inline int task_avdcache_search(struct task_security_struct *tsec,
				       struct inode_security_struct *isec,
				       struct avdc_entry **avdc)
{
	int orig, iter;

	/* focused on path walk optimization, only cache directories */
	if (isec->sclass != SECCLASS_DIR)
		return -ENOENT;

	if (unlikely(tsec->sid != tsec->avdcache.sid ||
		     tsec->avdcache.seqno != avc_policy_seqno())) {
		task_avdcache_reset(tsec);
		return -ENOENT;
	}

	orig = iter = tsec->avdcache.dir_spot;
	do {
		if (tsec->avdcache.dir[iter].isid == isec->sid) {
			/* cache hit */
			tsec->avdcache.dir_spot = iter;
			*avdc = &tsec->avdcache.dir[iter];
			return 0;
		}
		iter = (iter - 1) & (TSEC_AVDC_DIR_SIZE - 1);
	} while (iter != orig);

	return -ENOENT;
}

/**
 * task_avdcache_update - Update the task's AVD cache
 * @tsec: the task's security state
 * @isec: the inode associated with the cache entry
 * @avd: the AVD to cache
 * @audited: the permission audit bitmask to cache
 *
 * Update the AVD cache in @tsec with the @avdc and @audited info associated
 * with @isec.
 */
static inline void task_avdcache_update(struct task_security_struct *tsec,
					struct inode_security_struct *isec,
					struct av_decision *avd,
					u32 audited)
{
	int spot;

	/* focused on path walk optimization, only cache directories */
	if (isec->sclass != SECCLASS_DIR)
		return;

	/* update cache */
	spot = (tsec->avdcache.dir_spot + 1) & (TSEC_AVDC_DIR_SIZE - 1);
	tsec->avdcache.dir_spot = spot;
	tsec->avdcache.dir[spot].isid = isec->sid;
	tsec->avdcache.dir[spot].audited = audited;
	tsec->avdcache.dir[spot].allowed = avd->allowed;
	tsec->avdcache.dir[spot].permissive = avd->flags & AVD_FLAGS_PERMISSIVE;
}

/**
 * selinux_inode_permission - Check if the current task can access an inode
 * @inode: the inode that is being accessed
 * @requested: the accesses being requested
 *
 * Check if the current task is allowed to access @inode according to
 * @requested.  Returns 0 if allowed, negative values otherwise.
 */
static int selinux_inode_permission(struct inode *inode, int requested)
{
	int mask;
	u32 perms;
	bool from_access;
	bool no_block = mask & MAY_NOT_BLOCK;
	struct task_security_struct *tsec;
	struct inode_security_struct *isec;
	u32 sid = current_sid();
	struct av_decision avd;
	struct avdc_entry *avdc;
	int rc, rc2;
	u32 audited, denied;

	from_access = mask & MAY_ACCESS;
	mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);
	mask = requested & (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);

	/* No permission to check.  Existence test. */
	if (!mask)
		return 0;

	if (unlikely(IS_PRIVATE(inode)))
		return 0;

	perms = file_mask_to_av(inode->i_mode, mask);

	isec = inode_security_rcu(inode, no_block);
	isec = inode_security_rcu(inode, requested & MAY_NOT_BLOCK);
	if (IS_ERR(isec))
		return PTR_ERR(isec);
	tsec = selinux_cred(current_cred());
	perms = file_mask_to_av(inode->i_mode, mask);

	rc = avc_has_perm_noaudit(sid, isec->sid, isec->sclass, perms, 0,
				  &avd);
	rc = task_avdcache_search(tsec, isec, &avdc);
	if (likely(!rc)) {
		/* Cache hit. */
		audited = perms & avdc->audited;
		denied = perms & ~avdc->allowed;
		if (unlikely(denied && enforcing_enabled() &&
			     !avdc->permissive))
			rc = -EACCES;
	} else {
		struct av_decision avd;

		/* Cache miss. */
		rc = avc_has_perm_noaudit(tsec->sid, isec->sid, isec->sclass,
					  perms, 0, &avd);
		audited = avc_audit_required(perms, &avd, rc,
				     from_access ? FILE__AUDIT_ACCESS : 0,
			(requested & MAY_ACCESS) ? FILE__AUDIT_ACCESS : 0,
			&denied);
		task_avdcache_update(tsec, isec, &avd, audited);
	}

	if (likely(!audited))
		return rc;

	rc2 = audit_inode_permission(inode, perms, audited, denied, rc);
	if (rc2)
		return rc2;

	return rc;
}

+6 −7
Original line number Diff line number Diff line
@@ -130,7 +130,7 @@ static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid)
{
	int ret;
	struct sel_ib_pkey *pkey;
	struct sel_ib_pkey *new = NULL;
	struct sel_ib_pkey *new;
	unsigned long flags;

	spin_lock_irqsave(&sel_ib_pkey_lock, flags);
@@ -146,12 +146,11 @@ static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid)
	if (ret)
		goto out;

	new = kmalloc(sizeof(*new), GFP_ATOMIC);
	if (!new) {
		/* If this memory allocation fails still return 0. The SID
		 * is valid, it just won't be added to the cache.
		 */
	new = kzalloc(sizeof(*new), GFP_ATOMIC);
	if (!new) {
		ret = -ENOMEM;
		goto out;
	}

@@ -184,7 +183,7 @@ int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *sid)

	rcu_read_lock();
	pkey = sel_ib_pkey_find(subnet_prefix, pkey_num);
	if (pkey) {
	if (likely(pkey)) {
		*sid = pkey->psec.sid;
		rcu_read_unlock();
		return 0;
+1 −1
Original line number Diff line number Diff line
@@ -21,6 +21,6 @@

void sel_netnode_flush(void);

int sel_netnode_sid(void *addr, u16 family, u32 *sid);
int sel_netnode_sid(const void *addr, u16 family, u32 *sid);

#endif
+15 −1
Original line number Diff line number Diff line
@@ -29,6 +29,13 @@
#include "flask.h"
#include "avc.h"

struct avdc_entry {
	u32 isid; /* inode SID */
	u32 allowed; /* allowed permission bitmask */
	u32 audited; /* audited permission bitmask */
	bool permissive; /* AVC permissive flag */
};

struct task_security_struct {
	u32 osid; /* SID prior to last execve */
	u32 sid; /* current SID */
@@ -36,6 +43,13 @@ struct task_security_struct {
	u32 create_sid; /* fscreate SID */
	u32 keycreate_sid; /* keycreate SID */
	u32 sockcreate_sid; /* fscreate SID */
#define TSEC_AVDC_DIR_SIZE (1 << 2)
	struct {
		u32 sid; /* current SID for cached entries */
		u32 seqno; /* AVC sequence number */
		unsigned int dir_spot; /* dir cache index to check first */
		struct avdc_entry dir[TSEC_AVDC_DIR_SIZE]; /* dir entries */
	} avdcache;
} __randomize_layout;

enum label_initialized {
@@ -82,7 +96,7 @@ struct ipc_security_struct {
};

struct netif_security_struct {
	struct net *ns; /* network namespace */
	const struct net *ns; /* network namespace */
	int ifindex; /* device index */
	u32 sid; /* SID for this interface */
};
+1 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ enum {
	POLICYDB_CAP_USERSPACE_INITIAL_CONTEXT,
	POLICYDB_CAP_NETLINK_XPERM,
	POLICYDB_CAP_NETIF_WILDCARD,
	POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
	__POLICYDB_CAP_MAX
};
#define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
Loading