Commit 60144b23 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull selinux fixes from Paul Moore:
 "Three SELinux fixes.

  These fix known problems relating to (re)loading SELinux policy or
  changing the policy booleans, and pass our test suite without problem"

* tag 'selinux-pr-20210409' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux:
  selinux: fix race between old and new sidtab
  selinux: fix cond_list corruption when changing booleans
  selinux: make nslot handling in avtab more robust
parents 189fefc7 9ad6e9cb
Loading
Loading
Loading
Loading
+33 −68
Original line number Diff line number Diff line
@@ -109,7 +109,7 @@ static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_dat
	struct avtab_node *prev, *cur, *newnode;
	u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD);

	if (!h)
	if (!h || !h->nslot)
		return -EINVAL;

	hvalue = avtab_hash(key, h->mask);
@@ -154,7 +154,7 @@ avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, struct avtab_datu
	struct avtab_node *prev, *cur;
	u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD);

	if (!h)
	if (!h || !h->nslot)
		return NULL;
	hvalue = avtab_hash(key, h->mask);
	for (prev = NULL, cur = h->htable[hvalue];
@@ -184,7 +184,7 @@ struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *key)
	struct avtab_node *cur;
	u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD);

	if (!h)
	if (!h || !h->nslot)
		return NULL;

	hvalue = avtab_hash(key, h->mask);
@@ -220,7 +220,7 @@ avtab_search_node(struct avtab *h, struct avtab_key *key)
	struct avtab_node *cur;
	u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD);

	if (!h)
	if (!h || !h->nslot)
		return NULL;

	hvalue = avtab_hash(key, h->mask);
@@ -295,6 +295,7 @@ void avtab_destroy(struct avtab *h)
	}
	kvfree(h->htable);
	h->htable = NULL;
	h->nel = 0;
	h->nslot = 0;
	h->mask = 0;
}
@@ -303,88 +304,52 @@ void avtab_init(struct avtab *h)
{
	h->htable = NULL;
	h->nel = 0;
	h->nslot = 0;
	h->mask = 0;
}

int avtab_alloc(struct avtab *h, u32 nrules)
static int avtab_alloc_common(struct avtab *h, u32 nslot)
{
	u32 mask = 0;
	u32 shift = 0;
	u32 work = nrules;
	u32 nslot = 0;

	if (nrules == 0)
		goto avtab_alloc_out;

	while (work) {
		work  = work >> 1;
		shift++;
	}
	if (shift > 2)
		shift = shift - 2;
	nslot = 1 << shift;
	if (nslot > MAX_AVTAB_HASH_BUCKETS)
		nslot = MAX_AVTAB_HASH_BUCKETS;
	mask = nslot - 1;
	if (!nslot)
		return 0;

	h->htable = kvcalloc(nslot, sizeof(void *), GFP_KERNEL);
	if (!h->htable)
		return -ENOMEM;

 avtab_alloc_out:
	h->nel = 0;
	h->nslot = nslot;
	h->mask = mask;
	pr_debug("SELinux: %d avtab hash slots, %d rules.\n",
	       h->nslot, nrules);
	h->mask = nslot - 1;
	return 0;
}

int avtab_duplicate(struct avtab *new, struct avtab *orig)
int avtab_alloc(struct avtab *h, u32 nrules)
{
	int i;
	struct avtab_node *node, *tmp, *tail;

	memset(new, 0, sizeof(*new));
	int rc;
	u32 nslot = 0;

	new->htable = kvcalloc(orig->nslot, sizeof(void *), GFP_KERNEL);
	if (!new->htable)
		return -ENOMEM;
	new->nslot = orig->nslot;
	new->mask = orig->mask;

	for (i = 0; i < orig->nslot; i++) {
		tail = NULL;
		for (node = orig->htable[i]; node; node = node->next) {
			tmp = kmem_cache_zalloc(avtab_node_cachep, GFP_KERNEL);
			if (!tmp)
				goto error;
			tmp->key = node->key;
			if (tmp->key.specified & AVTAB_XPERMS) {
				tmp->datum.u.xperms =
					kmem_cache_zalloc(avtab_xperms_cachep,
							GFP_KERNEL);
				if (!tmp->datum.u.xperms) {
					kmem_cache_free(avtab_node_cachep, tmp);
					goto error;
				}
				tmp->datum.u.xperms = node->datum.u.xperms;
			} else
				tmp->datum.u.data = node->datum.u.data;

			if (tail)
				tail->next = tmp;
			else
				new->htable[i] = tmp;

			tail = tmp;
			new->nel++;
	if (nrules != 0) {
		u32 shift = 1;
		u32 work = nrules >> 3;
		while (work) {
			work >>= 1;
			shift++;
		}
		nslot = 1 << shift;
		if (nslot > MAX_AVTAB_HASH_BUCKETS)
			nslot = MAX_AVTAB_HASH_BUCKETS;

		rc = avtab_alloc_common(h, nslot);
		if (rc)
			return rc;
	}

	pr_debug("SELinux: %d avtab hash slots, %d rules.\n", nslot, nrules);
	return 0;
error:
	avtab_destroy(new);
	return -ENOMEM;
}

int avtab_alloc_dup(struct avtab *new, const struct avtab *orig)
{
	return avtab_alloc_common(new, orig->nslot);
}

void avtab_hash_eval(struct avtab *h, char *tag)
+1 −1
Original line number Diff line number Diff line
@@ -89,7 +89,7 @@ struct avtab {

void avtab_init(struct avtab *h);
int avtab_alloc(struct avtab *, u32);
int avtab_duplicate(struct avtab *new, struct avtab *orig);
int avtab_alloc_dup(struct avtab *new, const struct avtab *orig);
struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *k);
void avtab_destroy(struct avtab *h);
void avtab_hash_eval(struct avtab *h, char *tag);
+6 −6
Original line number Diff line number Diff line
@@ -605,7 +605,6 @@ static int cond_dup_av_list(struct cond_av_list *new,
			struct cond_av_list *orig,
			struct avtab *avtab)
{
	struct avtab_node *avnode;
	u32 i;

	memset(new, 0, sizeof(*new));
@@ -615,10 +614,11 @@ static int cond_dup_av_list(struct cond_av_list *new,
		return -ENOMEM;

	for (i = 0; i < orig->len; i++) {
		avnode = avtab_search_node(avtab, &orig->nodes[i]->key);
		if (WARN_ON(!avnode))
			return -EINVAL;
		new->nodes[i] = avnode;
		new->nodes[i] = avtab_insert_nonunique(avtab,
						       &orig->nodes[i]->key,
						       &orig->nodes[i]->datum);
		if (!new->nodes[i])
			return -ENOMEM;
		new->len++;
	}

@@ -630,7 +630,7 @@ static int duplicate_policydb_cond_list(struct policydb *newp,
{
	int rc, i, j;

	rc = avtab_duplicate(&newp->te_cond_avtab, &origp->te_cond_avtab);
	rc = avtab_alloc_dup(&newp->te_cond_avtab, &origp->te_cond_avtab);
	if (rc)
		return rc;

+120 −37
Original line number Diff line number Diff line
@@ -1552,6 +1552,7 @@ static int security_context_to_sid_core(struct selinux_state *state,
		if (!str)
			goto out;
	}
retry:
	rcu_read_lock();
	policy = rcu_dereference(state->policy);
	policydb = &policy->policydb;
@@ -1565,6 +1566,15 @@ static int security_context_to_sid_core(struct selinux_state *state,
	} else if (rc)
		goto out_unlock;
	rc = sidtab_context_to_sid(sidtab, &context, sid);
	if (rc == -ESTALE) {
		rcu_read_unlock();
		if (context.str) {
			str = context.str;
			context.str = NULL;
		}
		context_destroy(&context);
		goto retry;
	}
	context_destroy(&context);
out_unlock:
	rcu_read_unlock();
@@ -1714,7 +1724,7 @@ static int security_compute_sid(struct selinux_state *state,
	struct selinux_policy *policy;
	struct policydb *policydb;
	struct sidtab *sidtab;
	struct class_datum *cladatum = NULL;
	struct class_datum *cladatum;
	struct context *scontext, *tcontext, newcontext;
	struct sidtab_entry *sentry, *tentry;
	struct avtab_key avkey;
@@ -1736,6 +1746,8 @@ static int security_compute_sid(struct selinux_state *state,
		goto out;
	}

retry:
	cladatum = NULL;
	context_init(&newcontext);

	rcu_read_lock();
@@ -1880,6 +1892,11 @@ static int security_compute_sid(struct selinux_state *state,
	}
	/* Obtain the sid for the context. */
	rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid);
	if (rc == -ESTALE) {
		rcu_read_unlock();
		context_destroy(&newcontext);
		goto retry;
	}
out_unlock:
	rcu_read_unlock();
	context_destroy(&newcontext);
@@ -2192,6 +2209,7 @@ void selinux_policy_commit(struct selinux_state *state,
			   struct selinux_load_state *load_state)
{
	struct selinux_policy *oldpolicy, *newpolicy = load_state->policy;
	unsigned long flags;
	u32 seqno;

	oldpolicy = rcu_dereference_protected(state->policy,
@@ -2213,7 +2231,13 @@ void selinux_policy_commit(struct selinux_state *state,
	seqno = newpolicy->latest_granting;

	/* Install the new policy. */
	if (oldpolicy) {
		sidtab_freeze_begin(oldpolicy->sidtab, &flags);
		rcu_assign_pointer(state->policy, newpolicy);
		sidtab_freeze_end(oldpolicy->sidtab, &flags);
	} else {
		rcu_assign_pointer(state->policy, newpolicy);
	}

	/* Load the policycaps from the new policy */
	security_load_policycaps(state, newpolicy);
@@ -2357,13 +2381,15 @@ int security_port_sid(struct selinux_state *state,
	struct policydb *policydb;
	struct sidtab *sidtab;
	struct ocontext *c;
	int rc = 0;
	int rc;

	if (!selinux_initialized(state)) {
		*out_sid = SECINITSID_PORT;
		return 0;
	}

retry:
	rc = 0;
	rcu_read_lock();
	policy = rcu_dereference(state->policy);
	policydb = &policy->policydb;
@@ -2382,6 +2408,10 @@ int security_port_sid(struct selinux_state *state,
		if (!c->sid[0]) {
			rc = sidtab_context_to_sid(sidtab, &c->context[0],
						   &c->sid[0]);
			if (rc == -ESTALE) {
				rcu_read_unlock();
				goto retry;
			}
			if (rc)
				goto out;
		}
@@ -2408,13 +2438,15 @@ int security_ib_pkey_sid(struct selinux_state *state,
	struct policydb *policydb;
	struct sidtab *sidtab;
	struct ocontext *c;
	int rc = 0;
	int rc;

	if (!selinux_initialized(state)) {
		*out_sid = SECINITSID_UNLABELED;
		return 0;
	}

retry:
	rc = 0;
	rcu_read_lock();
	policy = rcu_dereference(state->policy);
	policydb = &policy->policydb;
@@ -2435,6 +2467,10 @@ int security_ib_pkey_sid(struct selinux_state *state,
			rc = sidtab_context_to_sid(sidtab,
						   &c->context[0],
						   &c->sid[0]);
			if (rc == -ESTALE) {
				rcu_read_unlock();
				goto retry;
			}
			if (rc)
				goto out;
		}
@@ -2460,13 +2496,15 @@ int security_ib_endport_sid(struct selinux_state *state,
	struct policydb *policydb;
	struct sidtab *sidtab;
	struct ocontext *c;
	int rc = 0;
	int rc;

	if (!selinux_initialized(state)) {
		*out_sid = SECINITSID_UNLABELED;
		return 0;
	}

retry:
	rc = 0;
	rcu_read_lock();
	policy = rcu_dereference(state->policy);
	policydb = &policy->policydb;
@@ -2487,6 +2525,10 @@ int security_ib_endport_sid(struct selinux_state *state,
		if (!c->sid[0]) {
			rc = sidtab_context_to_sid(sidtab, &c->context[0],
						   &c->sid[0]);
			if (rc == -ESTALE) {
				rcu_read_unlock();
				goto retry;
			}
			if (rc)
				goto out;
		}
@@ -2510,7 +2552,7 @@ int security_netif_sid(struct selinux_state *state,
	struct selinux_policy *policy;
	struct policydb *policydb;
	struct sidtab *sidtab;
	int rc = 0;
	int rc;
	struct ocontext *c;

	if (!selinux_initialized(state)) {
@@ -2518,6 +2560,8 @@ int security_netif_sid(struct selinux_state *state,
		return 0;
	}

retry:
	rc = 0;
	rcu_read_lock();
	policy = rcu_dereference(state->policy);
	policydb = &policy->policydb;
@@ -2534,10 +2578,18 @@ int security_netif_sid(struct selinux_state *state,
		if (!c->sid[0] || !c->sid[1]) {
			rc = sidtab_context_to_sid(sidtab, &c->context[0],
						   &c->sid[0]);
			if (rc == -ESTALE) {
				rcu_read_unlock();
				goto retry;
			}
			if (rc)
				goto out;
			rc = sidtab_context_to_sid(sidtab, &c->context[1],
						   &c->sid[1]);
			if (rc == -ESTALE) {
				rcu_read_unlock();
				goto retry;
			}
			if (rc)
				goto out;
		}
@@ -2587,6 +2639,7 @@ int security_node_sid(struct selinux_state *state,
		return 0;
	}

retry:
	rcu_read_lock();
	policy = rcu_dereference(state->policy);
	policydb = &policy->policydb;
@@ -2635,6 +2688,10 @@ int security_node_sid(struct selinux_state *state,
			rc = sidtab_context_to_sid(sidtab,
						   &c->context[0],
						   &c->sid[0]);
			if (rc == -ESTALE) {
				rcu_read_unlock();
				goto retry;
			}
			if (rc)
				goto out;
		}
@@ -2676,18 +2733,24 @@ int security_get_user_sids(struct selinux_state *state,
	struct sidtab *sidtab;
	struct context *fromcon, usercon;
	u32 *mysids = NULL, *mysids2, sid;
	u32 mynel = 0, maxnel = SIDS_NEL;
	u32 i, j, mynel, maxnel = SIDS_NEL;
	struct user_datum *user;
	struct role_datum *role;
	struct ebitmap_node *rnode, *tnode;
	int rc = 0, i, j;
	int rc;

	*sids = NULL;
	*nel = 0;

	if (!selinux_initialized(state))
		goto out;
		return 0;

	mysids = kcalloc(maxnel, sizeof(*mysids), GFP_KERNEL);
	if (!mysids)
		return -ENOMEM;

retry:
	mynel = 0;
	rcu_read_lock();
	policy = rcu_dereference(state->policy);
	policydb = &policy->policydb;
@@ -2707,11 +2770,6 @@ int security_get_user_sids(struct selinux_state *state,

	usercon.user = user->value;

	rc = -ENOMEM;
	mysids = kcalloc(maxnel, sizeof(*mysids), GFP_ATOMIC);
	if (!mysids)
		goto out_unlock;

	ebitmap_for_each_positive_bit(&user->roles, rnode, i) {
		role = policydb->role_val_to_struct[i];
		usercon.role = i + 1;
@@ -2723,6 +2781,10 @@ int security_get_user_sids(struct selinux_state *state,
				continue;

			rc = sidtab_context_to_sid(sidtab, &usercon, &sid);
			if (rc == -ESTALE) {
				rcu_read_unlock();
				goto retry;
			}
			if (rc)
				goto out_unlock;
			if (mynel < maxnel) {
@@ -2745,14 +2807,14 @@ int security_get_user_sids(struct selinux_state *state,
	rcu_read_unlock();
	if (rc || !mynel) {
		kfree(mysids);
		goto out;
		return rc;
	}

	rc = -ENOMEM;
	mysids2 = kcalloc(mynel, sizeof(*mysids2), GFP_KERNEL);
	if (!mysids2) {
		kfree(mysids);
		goto out;
		return rc;
	}
	for (i = 0, j = 0; i < mynel; i++) {
		struct av_decision dummy_avd;
@@ -2765,12 +2827,10 @@ int security_get_user_sids(struct selinux_state *state,
			mysids2[j++] = mysids[i];
		cond_resched();
	}
	rc = 0;
	kfree(mysids);
	*sids = mysids2;
	*nel = j;
out:
	return rc;
	return 0;
}

/**
@@ -2783,6 +2843,9 @@ int security_get_user_sids(struct selinux_state *state,
 * Obtain a SID to use for a file in a filesystem that
 * cannot support xattr or use a fixed labeling behavior like
 * transition SIDs or task SIDs.
 *
 * WARNING: This function may return -ESTALE, indicating that the caller
 * must retry the operation after re-acquiring the policy pointer!
 */
static inline int __security_genfs_sid(struct selinux_policy *policy,
				       const char *fstype,
@@ -2861,11 +2924,13 @@ int security_genfs_sid(struct selinux_state *state,
		return 0;
	}

	do {
		rcu_read_lock();
		policy = rcu_dereference(state->policy);
	retval = __security_genfs_sid(policy,
				fstype, path, orig_sclass, sid);
		retval = __security_genfs_sid(policy, fstype, path,
					      orig_sclass, sid);
		rcu_read_unlock();
	} while (retval == -ESTALE);
	return retval;
}

@@ -2888,7 +2953,7 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb)
	struct selinux_policy *policy;
	struct policydb *policydb;
	struct sidtab *sidtab;
	int rc = 0;
	int rc;
	struct ocontext *c;
	struct superblock_security_struct *sbsec = sb->s_security;
	const char *fstype = sb->s_type->name;
@@ -2899,6 +2964,8 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb)
		return 0;
	}

retry:
	rc = 0;
	rcu_read_lock();
	policy = rcu_dereference(state->policy);
	policydb = &policy->policydb;
@@ -2916,6 +2983,10 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb)
		if (!c->sid[0]) {
			rc = sidtab_context_to_sid(sidtab, &c->context[0],
						   &c->sid[0]);
			if (rc == -ESTALE) {
				rcu_read_unlock();
				goto retry;
			}
			if (rc)
				goto out;
		}
@@ -2923,6 +2994,10 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb)
	} else {
		rc = __security_genfs_sid(policy, fstype, "/",
					SECCLASS_DIR, &sbsec->sid);
		if (rc == -ESTALE) {
			rcu_read_unlock();
			goto retry;
		}
		if (rc) {
			sbsec->behavior = SECURITY_FS_USE_NONE;
			rc = 0;
@@ -3132,12 +3207,13 @@ int security_sid_mls_copy(struct selinux_state *state,
	u32 len;
	int rc;

	rc = 0;
	if (!selinux_initialized(state)) {
		*new_sid = sid;
		goto out;
		return 0;
	}

retry:
	rc = 0;
	context_init(&newcon);

	rcu_read_lock();
@@ -3196,10 +3272,14 @@ int security_sid_mls_copy(struct selinux_state *state,
		}
	}
	rc = sidtab_context_to_sid(sidtab, &newcon, new_sid);
	if (rc == -ESTALE) {
		rcu_read_unlock();
		context_destroy(&newcon);
		goto retry;
	}
out_unlock:
	rcu_read_unlock();
	context_destroy(&newcon);
out:
	return rc;
}

@@ -3792,6 +3872,8 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state,
		return 0;
	}

retry:
	rc = 0;
	rcu_read_lock();
	policy = rcu_dereference(state->policy);
	policydb = &policy->policydb;
@@ -3818,23 +3900,24 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state,
				goto out;
		}
		rc = -EIDRM;
		if (!mls_context_isvalid(policydb, &ctx_new))
			goto out_free;
		if (!mls_context_isvalid(policydb, &ctx_new)) {
			ebitmap_destroy(&ctx_new.range.level[0].cat);
			goto out;
		}

		rc = sidtab_context_to_sid(sidtab, &ctx_new, sid);
		ebitmap_destroy(&ctx_new.range.level[0].cat);
		if (rc == -ESTALE) {
			rcu_read_unlock();
			goto retry;
		}
		if (rc)
			goto out_free;
			goto out;

		security_netlbl_cache_add(secattr, *sid);

		ebitmap_destroy(&ctx_new.range.level[0].cat);
	} else
		*sid = SECSID_NULL;

	rcu_read_unlock();
	return 0;
out_free:
	ebitmap_destroy(&ctx_new.range.level[0].cat);
out:
	rcu_read_unlock();
	return rc;
+21 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ int sidtab_init(struct sidtab *s)
	for (i = 0; i < SECINITSID_NUM; i++)
		s->isids[i].set = 0;

	s->frozen = false;
	s->count = 0;
	s->convert = NULL;
	hash_init(s->context_to_sid);
@@ -281,6 +282,15 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context,
	if (*sid)
		goto out_unlock;

	if (unlikely(s->frozen)) {
		/*
		 * This sidtab is now frozen - tell the caller to abort and
		 * get the new one.
		 */
		rc = -ESTALE;
		goto out_unlock;
	}

	count = s->count;
	convert = s->convert;

@@ -474,6 +484,17 @@ void sidtab_cancel_convert(struct sidtab *s)
	spin_unlock_irqrestore(&s->lock, flags);
}

void sidtab_freeze_begin(struct sidtab *s, unsigned long *flags) __acquires(&s->lock)
{
	spin_lock_irqsave(&s->lock, *flags);
	s->frozen = true;
	s->convert = NULL;
}
void sidtab_freeze_end(struct sidtab *s, unsigned long *flags) __releases(&s->lock)
{
	spin_unlock_irqrestore(&s->lock, *flags);
}

static void sidtab_destroy_entry(struct sidtab_entry *entry)
{
	context_destroy(&entry->context);
Loading