Commit 88fec352 authored by John Johansen's avatar John Johansen
Browse files

apparmor: make sure unix socket labeling is correctly updated.



When a unix socket is passed into a different confinement domain make
sure its cached mediation labeling is updated to correctly reflect
which domains are using the socket.

Fixes: c05e7058 ("apparmor: add fine grained af_unix mediation")
Signed-off-by: default avatarJohn Johansen <john.johansen@canonical.com>
parent 6456ccbd
Loading
Loading
Loading
Loading
+76 −3
Original line number Diff line number Diff line
@@ -646,6 +646,67 @@ int aa_unix_peer_perm(const struct cred *subj_cred,
			      peer_label);
}

/* sk_plabel for comparison only */
static void update_sk_ctx(struct sock *sk, struct aa_label *label,
			  struct aa_label *plabel)
{
	struct aa_label *l, *old;
	struct aa_sk_ctx *ctx = aa_sock(sk);
	bool update_sk;

	rcu_read_lock();
	update_sk = (plabel &&
		     (plabel != rcu_access_pointer(ctx->peer_lastupdate) ||
		      !aa_label_is_subset(plabel, rcu_dereference(ctx->peer)))) ||
	  !__aa_subj_label_is_cached(label, rcu_dereference(ctx->label));
	rcu_read_unlock();
	if (!update_sk)
		return;

	spin_lock(&unix_sk(sk)->lock);
	old = rcu_dereference_protected(ctx->label,
					lockdep_is_held(&unix_sk(sk)->lock));
	l = aa_label_merge(old, label, GFP_ATOMIC);
	if (l) {
		if (l != old) {
			rcu_assign_pointer(ctx->label, l);
			aa_put_label(old);
		} else
			aa_put_label(l);
	}
	if (plabel && rcu_access_pointer(ctx->peer_lastupdate) != plabel) {
		old = rcu_dereference_protected(ctx->peer, lockdep_is_held(&unix_sk(sk)->lock));

		if (old == plabel) {
			rcu_assign_pointer(ctx->peer_lastupdate, plabel);
		} else if (aa_label_is_subset(plabel, old)) {
			rcu_assign_pointer(ctx->peer_lastupdate, plabel);
			rcu_assign_pointer(ctx->peer, aa_get_label(plabel));
			aa_put_label(old);
		} /* else race or a subset - don't update */
	}
	spin_unlock(&unix_sk(sk)->lock);
}

static void update_peer_ctx(struct sock *sk, struct aa_sk_ctx *ctx,
			    struct aa_label *label)
{
	struct aa_label *l, *old;

	spin_lock(&unix_sk(sk)->lock);
	old = rcu_dereference_protected(ctx->peer,
					lockdep_is_held(&unix_sk(sk)->lock));
	l = aa_label_merge(old, label, GFP_ATOMIC);
	if (l) {
		if (l != old) {
			rcu_assign_pointer(ctx->peer, l);
			aa_put_label(old);
		} else
			aa_put_label(l);
	}
	spin_unlock(&unix_sk(sk)->lock);
}

/* This fn is only checked if something has changed in the security
 * boundaries. Otherwise cached info off file is sufficient
 */
@@ -655,6 +716,7 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label,
	struct socket *sock = (struct socket *) file->private_data;
	struct sockaddr_un *addr, *peer_addr;
	int addrlen, peer_addrlen;
	struct aa_label *plabel = NULL;
	struct sock *peer_sk = NULL;
	u32 sk_req = request & ~NET_PEER_MASK;
	struct path path;
@@ -666,7 +728,6 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label,
	AA_BUG(!sock->sk);
	AA_BUG(sock->sk->sk_family != PF_UNIX);

	/* TODO: update sock label with new task label */
	/* investigate only using lock via unix_peer_get()
	 * addr only needs the memory barrier, but need to investigate
	 * path
@@ -701,8 +762,12 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label,
			   unix_fs_perm(op, request, subj_cred, label,
					is_unix_fs(peer_sk) ? &peer_path : NULL));
	} else if (!is_sk_fs) {
		struct aa_label *plabel;
		struct aa_sk_ctx *pctx = aa_sock(peer_sk);

		rcu_read_lock();
		plabel = aa_get_label_rcu(&pctx->label);
		rcu_read_unlock();
		/* no fs check of aa_unix_peer_perm because conditions above
		 * ensure they will never be done
		 */
@@ -713,18 +778,26 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label,
					      peer_addr, peer_addrlen,
					      is_unix_fs(peer_sk) ?
							&peer_path : NULL,
					      pctx->label),
			       unix_peer_perm(file->f_cred, pctx->label, op,
					      plabel),
			       unix_peer_perm(file->f_cred, plabel, op,
					      MAY_READ | MAY_WRITE, peer_sk,
					      is_unix_fs(peer_sk) ?
							&peer_path : NULL,
					      addr, addrlen,
					      is_sk_fs ? &path : NULL,
					      label)));
		if (!error && !__aa_subj_label_is_cached(plabel, label))
			update_peer_ctx(peer_sk, pctx, label);
	}
	sock_put(peer_sk);

out:

	/* update peer cache to latest successful perm check */
	if (error == 0)
		update_sk_ctx(sock->sk, label, plabel);
	aa_put_label(plabel);

	return error;
}
+26 −9
Original line number Diff line number Diff line
@@ -561,19 +561,35 @@ static int __file_sock_perm(const char *op, const struct cred *subj_cred,
	return error;
}

/* wrapper fn to indicate semantics of the check */
static bool __subj_label_is_cached(struct aa_label *subj_label,
			    struct aa_label *obj_label)
{
	return aa_label_is_subset(obj_label, subj_label);
}

/* for now separate fn to indicate semantics of the check */
static bool __file_is_delegated(struct aa_label *obj_label)
{
	return unconfined(obj_label);
}

static bool __unix_needs_revalidation(struct file *file, struct aa_label *label,
				      u32 request)
{
	struct socket *sock = (struct socket *) file->private_data;

	lockdep_assert_in_rcu_read_lock();

	if (!S_ISSOCK(file_inode(file)->i_mode))
		return false;
	if (request & NET_PEER_MASK)
		return false;
	if (sock->sk->sk_family == PF_UNIX) {
		struct aa_sk_ctx *ctx = aa_sock(sock->sk);

		if (rcu_access_pointer(ctx->peer) !=
		    rcu_access_pointer(ctx->peer_lastupdate))
			return true;
		return !__aa_subj_label_is_cached(rcu_dereference(ctx->label),
						  label);
	}
	return false;
}

/**
 * aa_file_perm - do permission revalidation check & audit for @file
 * @op: operation being checked
@@ -612,14 +628,15 @@ int aa_file_perm(const char *op, const struct cred *subj_cred,
	 */
	denied = request & ~fctx->allow;
	if (unconfined(label) || __file_is_delegated(flabel) ||
	    (!denied && __subj_label_is_cached(label, flabel))) {
	    __unix_needs_revalidation(file, label, request) ||
	    (!denied && __aa_subj_label_is_cached(label, flabel))) {
		rcu_read_unlock();
		goto done;
	}

	/* slow path - revalidate access */
	flabel  = aa_get_newest_label(flabel);
	rcu_read_unlock();
	/* TODO: label cross check */

	if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry))
		error = __file_path_perm(op, subj_cred, label, flabel, file,
+7 −0
Original line number Diff line number Diff line
@@ -415,6 +415,13 @@ static inline void aa_put_label(struct aa_label *l)
		kref_put(&l->count, aa_label_kref);
}

/* wrapper fn to indicate semantics of the check */
static inline bool __aa_subj_label_is_cached(struct aa_label *subj_label,
					  struct aa_label *obj_label)
{
	return aa_label_is_subset(obj_label, subj_label);
}


struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp);
void aa_proxy_kref(struct kref *kref);
+3 −2
Original line number Diff line number Diff line
@@ -47,8 +47,9 @@
#define NET_PEER_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CONNECT |	\
		       AA_MAY_ACCEPT)
struct aa_sk_ctx {
	struct aa_label *label;
	struct aa_label *peer;
	struct aa_label __rcu *label;
	struct aa_label __rcu *peer;
	struct aa_label __rcu *peer_lastupdate;	/* ptr cmp only, no deref */
};

static inline struct aa_sk_ctx *aa_sock(const struct sock *sk)
+118 −47
Original line number Diff line number Diff line
@@ -508,7 +508,6 @@ static int apparmor_file_alloc_security(struct file *file)
	struct aa_file_ctx *ctx = file_ctx(file);
	struct aa_label *label = begin_current_label_crit_section();

	spin_lock_init(&ctx->lock);
	rcu_assign_pointer(ctx->label, aa_get_label(label));
	end_current_label_crit_section(label);
	return 0;
@@ -1076,12 +1075,29 @@ static int apparmor_userns_create(const struct cred *cred)
	return error;
}

static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t gfp)
{
	struct aa_sk_ctx *ctx = aa_sock(sk);
	struct aa_label *label;
	bool needput;

	label = __begin_current_label_crit_section(&needput);
	//spin_lock_init(&ctx->lock);
	rcu_assign_pointer(ctx->label, aa_get_label(label));
	rcu_assign_pointer(ctx->peer, NULL);
	rcu_assign_pointer(ctx->peer_lastupdate, NULL);
	__end_current_label_crit_section(label, needput);
	return 0;
}

static void apparmor_sk_free_security(struct sock *sk)
{
	struct aa_sk_ctx *ctx = aa_sock(sk);

	aa_put_label(ctx->label);
	aa_put_label(ctx->peer);
	/* dead these won't be updated any more */
	aa_put_label(rcu_dereference_protected(ctx->label, true));
	aa_put_label(rcu_dereference_protected(ctx->peer, true));
	aa_put_label(rcu_dereference_protected(ctx->peer_lastupdate, true));
}

/**
@@ -1095,13 +1111,22 @@ static void apparmor_sk_clone_security(const struct sock *sk,
	struct aa_sk_ctx *ctx = aa_sock(sk);
	struct aa_sk_ctx *new = aa_sock(newsk);

	if (new->label)
		aa_put_label(new->label);
	new->label = aa_get_label(ctx->label);
	/* not actually in use yet */
	if (rcu_access_pointer(ctx->label) != rcu_access_pointer(new->label)) {
		aa_put_label(rcu_dereference_protected(new->label, true));
		rcu_assign_pointer(new->label, aa_get_label_rcu(&ctx->label));
	}

	if (rcu_access_pointer(ctx->peer) != rcu_access_pointer(new->peer)) {
		aa_put_label(rcu_dereference_protected(new->peer, true));
		rcu_assign_pointer(new->peer, aa_get_label_rcu(&ctx->peer));
	}

	if (new->peer)
		aa_put_label(new->peer);
	new->peer = aa_get_label(ctx->peer);
	if (rcu_access_pointer(ctx->peer_lastupdate) != rcu_access_pointer(new->peer_lastupdate)) {
		aa_put_label(rcu_dereference_protected(new->peer_lastupdate, true));
		rcu_assign_pointer(new->peer_lastupdate,
				   aa_get_label_rcu(&ctx->peer_lastupdate));
	}
}

static int unix_connect_perm(const struct cred *cred, struct aa_label *label,
@@ -1112,11 +1137,15 @@ static int unix_connect_perm(const struct cred *cred, struct aa_label *label,

	error = aa_unix_peer_perm(cred, label, OP_CONNECT,
				(AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE),
				  sk, peer_sk, peer_ctx->label);
				  sk, peer_sk,
				  rcu_dereference_protected(peer_ctx->label,
				     lockdep_is_held(&unix_sk(peer_sk)->lock)));
	if (!is_unix_fs(peer_sk)) {
		last_error(error,
			   aa_unix_peer_perm(cred,
				peer_ctx->label, OP_CONNECT,
				rcu_dereference_protected(peer_ctx->label,
				     lockdep_is_held(&unix_sk(peer_sk)->lock)),
				OP_CONNECT,
				(AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE),
							  peer_sk, sk, label));
	}
@@ -1124,15 +1153,31 @@ static int unix_connect_perm(const struct cred *cred, struct aa_label *label,
	return error;
}

/* lockdep check in unix_connect_perm - push sks here to check */
static void unix_connect_peers(struct aa_sk_ctx *sk_ctx,
			       struct aa_sk_ctx *peer_ctx)
{
	/* Cross reference the peer labels for SO_PEERSEC */
	aa_put_label(peer_ctx->peer);
	aa_put_label(sk_ctx->peer);
	struct aa_label *label = rcu_dereference_protected(sk_ctx->label, true);

	aa_get_label(label);
	aa_put_label(rcu_dereference_protected(peer_ctx->peer,
					     true));
	rcu_assign_pointer(peer_ctx->peer, label);	/* transfer cnt */

	label = aa_get_label(rcu_dereference_protected(peer_ctx->label,
					     true));
	//spin_unlock(&peer_ctx->lock);

	peer_ctx->peer = aa_get_label(sk_ctx->label);
	sk_ctx->peer = aa_get_label(peer_ctx->label);
	//spin_lock(&sk_ctx->lock);
	aa_put_label(rcu_dereference_protected(sk_ctx->peer,
					       true));
	aa_put_label(rcu_dereference_protected(sk_ctx->peer_lastupdate,
					       true));

	rcu_assign_pointer(sk_ctx->peer, aa_get_label(label));
	rcu_assign_pointer(sk_ctx->peer_lastupdate, label);     /* transfer cnt */
	//spin_unlock(&sk_ctx->lock);
}

/**
@@ -1158,8 +1203,10 @@ static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk,
		return error;

	/* newsk doesn't go through post_create */
	AA_BUG(new_ctx->label);
	new_ctx->label = aa_get_label(peer_ctx->label);
	AA_BUG(rcu_access_pointer(new_ctx->label));
	rcu_assign_pointer(new_ctx->label,
			   aa_get_label(rcu_dereference_protected(peer_ctx->label,
								  true)));

	/* Cross reference the peer labels for SO_PEERSEC */
	unix_connect_peers(sk_ctx, new_ctx);
@@ -1184,11 +1231,14 @@ static int apparmor_unix_may_send(struct socket *sock, struct socket *peer)
	label = __begin_current_label_crit_section(&needput);
	error = xcheck(aa_unix_peer_perm(current_cred(),
				label, OP_SENDMSG, AA_MAY_SEND,
					 sock->sk, peer->sk, peer_ctx->label),
				sock->sk, peer->sk,
				rcu_dereference_protected(peer_ctx->label,
							  true)),
		       aa_unix_peer_perm(peer->file ? peer->file->f_cred : NULL,
					 peer_ctx->label, OP_SENDMSG,
					 AA_MAY_RECEIVE,
					 peer->sk, sock->sk, label));
				rcu_dereference_protected(peer_ctx->label,
							  true),
				OP_SENDMSG, AA_MAY_RECEIVE, peer->sk,
				sock->sk, label));
	__end_current_label_crit_section(label, needput);

	return error;
@@ -1246,8 +1296,9 @@ static int apparmor_socket_post_create(struct socket *sock, int family,
	if (sock->sk) {
		struct aa_sk_ctx *ctx = aa_sock(sock->sk);

		aa_put_label(ctx->label);
		ctx->label = aa_get_label(label);
		/* still not live */
		aa_put_label(rcu_dereference_protected(ctx->label, true));
		rcu_assign_pointer(ctx->label, aa_get_label(label));
	}
	aa_put_label(label);

@@ -1260,23 +1311,27 @@ static int apparmor_socket_socketpair(struct socket *socka,
	struct aa_sk_ctx *a_ctx = aa_sock(socka->sk);
	struct aa_sk_ctx *b_ctx = aa_sock(sockb->sk);
	struct aa_label *label;
	int error = 0;

	aa_put_label(a_ctx->label);
	aa_put_label(b_ctx->label);

	/* socks not live yet - initial values set in sk_alloc */
	label = begin_current_label_crit_section();
	a_ctx->label = aa_get_label(label);
	b_ctx->label = aa_get_label(label);
	if (rcu_access_pointer(a_ctx->label) != label) {
		AA_BUG("a_ctx != label");
		aa_put_label(rcu_dereference_protected(a_ctx->label, true));
		rcu_assign_pointer(a_ctx->label, aa_get_label(label));
	}
	if (rcu_access_pointer(b_ctx->label) != label) {
		AA_BUG("b_ctx != label");
		aa_put_label(rcu_dereference_protected(b_ctx->label, true));
		rcu_assign_pointer(b_ctx->label, aa_get_label(label));
	}

	if (socka->sk->sk_family == PF_UNIX) {
		/* unix socket pairs by-pass unix_stream_connect */
		if (!error)
		unix_connect_peers(a_ctx, b_ctx);
	}
	end_current_label_crit_section(label);

	return error;
	return 0;
}

/**
@@ -1430,6 +1485,7 @@ static int apparmor_socket_shutdown(struct socket *sock, int how)
static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
	struct aa_sk_ctx *ctx = aa_sock(sk);
	int error;

	if (!skb->secmark)
		return 0;
@@ -1438,11 +1494,15 @@ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
	 * If reach here before socket_post_create hook is called, in which
	 * case label is null, drop the packet.
	 */
	if (!ctx->label)
	if (!rcu_access_pointer(ctx->label))
		return -EACCES;

	return apparmor_secmark_check(ctx->label, OP_RECVMSG, AA_MAY_RECEIVE,
				      skb->secmark, sk);
	rcu_read_lock();
	error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_RECVMSG,
				       AA_MAY_RECEIVE, skb->secmark, sk);
	rcu_read_unlock();

	return error;
}
#endif

@@ -1452,8 +1512,8 @@ static struct aa_label *sk_peer_get_label(struct sock *sk)
	struct aa_sk_ctx *ctx = aa_sock(sk);
	struct aa_label *label = ERR_PTR(-ENOPROTOOPT);

	if (ctx->peer)
		return aa_get_label(ctx->peer);
	if (rcu_access_pointer(ctx->peer))
		return aa_get_label_rcu(&ctx->peer);

	if (sk->sk_family != PF_UNIX)
		return ERR_PTR(-ENOPROTOOPT);
@@ -1480,12 +1540,12 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,
	struct aa_label *label;
	struct aa_label *peer;

	label = begin_current_label_crit_section();
	peer = sk_peer_get_label(sock->sk);
	if (IS_ERR(peer)) {
		error = PTR_ERR(peer);
		goto done;
	}
	label = begin_current_label_crit_section();
	slen = aa_label_asxprint(&name, labels_ns(label), peer,
				 FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
				 FLAG_HIDDEN_UNCONFINED, GFP_KERNEL);
@@ -1506,9 +1566,9 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,
		error = -EFAULT;

done_put:
	end_current_label_crit_section(label);
	aa_put_label(peer);
done:
	end_current_label_crit_section(label);
	kfree(name);
	return error;
}
@@ -1544,8 +1604,9 @@ static void apparmor_sock_graft(struct sock *sk, struct socket *parent)
{
	struct aa_sk_ctx *ctx = aa_sock(sk);

	if (!ctx->label)
		ctx->label = aa_get_current_label();
	/* setup - not live */
	if (!rcu_access_pointer(ctx->label))
		rcu_assign_pointer(ctx->label, aa_get_current_label());
}

#ifdef CONFIG_NETWORK_SECMARK
@@ -1553,12 +1614,17 @@ static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb
				      struct request_sock *req)
{
	struct aa_sk_ctx *ctx = aa_sock(sk);
	int error;

	if (!skb->secmark)
		return 0;

	return apparmor_secmark_check(ctx->label, OP_CONNECT, AA_MAY_CONNECT,
				      skb->secmark, sk);
	rcu_read_lock();
	error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_CONNECT,
				       AA_MAY_CONNECT, skb->secmark, sk);
	rcu_read_unlock();

	return error;
}
#endif

@@ -1615,6 +1681,7 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = {
	LSM_HOOK_INIT(getprocattr, apparmor_getprocattr),
	LSM_HOOK_INIT(setprocattr, apparmor_setprocattr),

	LSM_HOOK_INIT(sk_alloc_security, apparmor_sk_alloc_security),
	LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security),
	LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security),

@@ -2266,6 +2333,7 @@ static unsigned int apparmor_ip_postroute(void *priv,
{
	struct aa_sk_ctx *ctx;
	struct sock *sk;
	int error;

	if (!skb->secmark)
		return NF_ACCEPT;
@@ -2275,8 +2343,11 @@ static unsigned int apparmor_ip_postroute(void *priv,
		return NF_ACCEPT;

	ctx = aa_sock(sk);
	if (!apparmor_secmark_check(ctx->label, OP_SENDMSG, AA_MAY_SEND,
				    skb->secmark, sk))
	rcu_read_lock();
	error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_SENDMSG,
				       AA_MAY_SEND, skb->secmark, sk);
	rcu_read_unlock();
	if (!error)
		return NF_ACCEPT;

	return NF_DROP_ERR(-ECONNREFUSED);
Loading