summaryrefslogtreecommitdiff
path: root/security/apparmor/lsm.c
diff options
context:
space:
mode:
authorJohn Johansen <john.johansen@canonical.com>2022-09-07 12:46:30 -0700
committerJohn Johansen <john.johansen@canonical.com>2025-01-18 06:47:12 -0800
commitc05e705812d179f4b85aeacc34a555a42bc4f9ac (patch)
tree82878c6d4d617e5c4b77e3ef81b612dcc11b4235 /security/apparmor/lsm.c
parentb4940d913cc2c67f8f6bf17abbf3e5301f95e260 (diff)
apparmor: add fine grained af_unix mediation
Extend af_unix mediation to support fine grained controls based on the type (abstract, anonymous, fs), the address, and the labeling on the socket. This allows for using socket addresses to label and the socket and control which subjects can communicate. The unix rule format follows standard apparmor rules except that fs based unix sockets can be mediated by existing file rules. None fs unix sockets can be mediated by a unix socket rule. Where The address of an abstract unix domain socket begins with the @ character, similar to how they are reported (as paths) by netstat -x. The address then follows and may contain pattern matching and any characters including the null character. In apparmor null characters must be specified by using an escape sequence \000 or \x00. The pattern matching is the same as is used by file path matching so * will not match / even though it has no special meaning with in an abstract socket name. Eg. allow unix addr=@*, Autobound unix domain sockets have a unix sun_path assigned to them by the kernel, as such specifying a policy based address is not possible. The autobinding of sockets can be controlled by specifying the special auto keyword. Eg. allow unix addr=auto, To indicate that the rule only applies to auto binding of unix domain sockets. It is important to note this only applies to the bind permission as once the socket is bound to an address it is indistinguishable from a socket that have an addr bound with a specified name. When the auto keyword is used with other permissions or as part of a peer addr it will be replaced with a pattern that can match an autobound socket. Eg. For some kernels allow unix rw addr=auto, It is important to note, this pattern may match abstract sockets that were not autobound but have an addr that fits what is generated by the kernel when autobinding a socket. Anonymous unix domain sockets have no sun_path associated with the socket address, however it can be specified with the special none keyword to indicate the rule only applies to anonymous unix domain sockets. Eg. allow unix addr=none, If the address component of a rule is not specified then the rule applies to autobind, abstract and anonymous sockets. The label on the socket can be compared using the standard label= rule conditional. Eg. allow unix addr=@foo peer=(label=bar), see man apparmor.d for full syntax description. Signed-off-by: John Johansen <john.johansen@canonical.com>
Diffstat (limited to 'security/apparmor/lsm.c')
-rw-r--r--security/apparmor/lsm.c163
1 files changed, 156 insertions, 7 deletions
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index f7b2d4bb1d97..0b4f7e2e4135 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -26,6 +26,7 @@
#include <uapi/linux/mount.h>
#include <uapi/linux/lsm.h>
+#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/apparmorfs.h"
#include "include/audit.h"
@@ -1088,6 +1089,94 @@ static void apparmor_sk_clone_security(const struct sock *sk,
new->peer = aa_get_label(ctx->peer);
}
+static int unix_connect_perm(const struct cred *cred, struct aa_label *label,
+ struct sock *sk, struct sock *peer_sk)
+{
+ struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk);
+ int error;
+
+ error = aa_unix_peer_perm(cred, label, OP_CONNECT,
+ (AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE),
+ sk, peer_sk, NULL);
+ if (!is_unix_fs(peer_sk)) {
+ last_error(error,
+ aa_unix_peer_perm(cred,
+ peer_ctx->label, OP_CONNECT,
+ (AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE),
+ peer_sk, sk, label));
+ }
+
+ return error;
+}
+
+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);
+
+ peer_ctx->peer = aa_get_label(sk_ctx->label);
+ sk_ctx->peer = aa_get_label(peer_ctx->label);
+}
+
+/**
+ * apparmor_unix_stream_connect - check perms before making unix domain conn
+ *
+ * peer is locked when this hook is called
+ */
+static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk,
+ struct sock *newsk)
+{
+ struct aa_sk_ctx *sk_ctx = aa_sock(sk);
+ struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk);
+ struct aa_sk_ctx *new_ctx = aa_sock(newsk);
+ struct aa_label *label;
+ int error;
+
+ label = __begin_current_label_crit_section();
+ error = unix_connect_perm(current_cred(), label, sk, peer_sk);
+ __end_current_label_crit_section(label);
+
+ if (error)
+ return error;
+
+ /* newsk doesn't go through post_create */
+ AA_BUG(new_ctx->label);
+ new_ctx->label = aa_get_label(peer_ctx->label);
+
+ /* Cross reference the peer labels for SO_PEERSEC */
+ unix_connect_peers(sk_ctx, new_ctx);
+
+ return 0;
+}
+
+/**
+ * apparmor_unix_may_send - check perms before conn or sending unix dgrams
+ *
+ * sock and peer are locked when this hook is called
+ *
+ * called by: dgram_connect peer setup but path not copied to newsk
+ */
+static int apparmor_unix_may_send(struct socket *sock, struct socket *peer)
+{
+ struct aa_sk_ctx *peer_ctx = aa_sock(peer->sk);
+ struct aa_label *label;
+ int error;
+
+ label = __begin_current_label_crit_section();
+ error = xcheck(aa_unix_peer_perm(current_cred(),
+ label, OP_SENDMSG, AA_MAY_SEND,
+ sock->sk, peer->sk, NULL),
+ aa_unix_peer_perm(peer->file ? peer->file->f_cred : NULL,
+ peer_ctx->label, OP_SENDMSG,
+ AA_MAY_RECEIVE,
+ peer->sk, sock->sk, label));
+ __end_current_label_crit_section(label);
+
+ return error;
+}
+
static int apparmor_socket_create(int family, int type, int protocol, int kern)
{
struct aa_label *label;
@@ -1100,8 +1189,13 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern)
label = begin_current_label_crit_section();
if (!unconfined(label)) {
- error = aa_af_perm(current_cred(), label, OP_CREATE,
- AA_MAY_CREATE, family, type, protocol);
+ if (family == PF_UNIX)
+ error = aa_unix_create_perm(label, family, type,
+ protocol);
+ else
+ error = aa_af_perm(current_cred(), label, OP_CREATE,
+ AA_MAY_CREATE, family, type,
+ protocol);
}
end_current_label_crit_section(label);
@@ -1143,6 +1237,34 @@ static int apparmor_socket_post_create(struct socket *sock, int family,
return 0;
}
+static int apparmor_socket_socketpair(struct socket *socka,
+ struct socket *sockb)
+{
+ 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);
+
+ label = begin_current_label_crit_section();
+ a_ctx->label = aa_get_label(label);
+ 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;
+}
+
+/**
+ * apparmor_socket_bind - check perms before bind addr to socket
+ */
static int apparmor_socket_bind(struct socket *sock,
struct sockaddr *address, int addrlen)
{
@@ -1151,6 +1273,8 @@ static int apparmor_socket_bind(struct socket *sock,
AA_BUG(!address);
AA_BUG(in_interrupt());
+ if (sock->sk->sk_family == PF_UNIX)
+ return aa_unix_bind_perm(sock, address, addrlen);
return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk);
}
@@ -1162,6 +1286,9 @@ static int apparmor_socket_connect(struct socket *sock,
AA_BUG(!address);
AA_BUG(in_interrupt());
+ /* PF_UNIX goes through unix_stream_connect && unix_may_send */
+ if (sock->sk->sk_family == PF_UNIX)
+ return 0;
return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk);
}
@@ -1171,6 +1298,8 @@ static int apparmor_socket_listen(struct socket *sock, int backlog)
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
+ if (sock->sk->sk_family == PF_UNIX)
+ return aa_unix_listen_perm(sock, backlog);
return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk);
}
@@ -1185,6 +1314,8 @@ static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
AA_BUG(!newsock);
AA_BUG(in_interrupt());
+ if (sock->sk->sk_family == PF_UNIX)
+ return aa_unix_accept_perm(sock, newsock);
return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk);
}
@@ -1196,6 +1327,9 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock,
AA_BUG(!msg);
AA_BUG(in_interrupt());
+ /* PF_UNIX goes through unix_may_send */
+ if (sock->sk->sk_family == PF_UNIX)
+ return 0;
return aa_sk_perm(op, request, sock->sk);
}
@@ -1218,6 +1352,8 @@ static int aa_sock_perm(const char *op, u32 request, struct socket *sock)
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
+ if (sock->sk->sk_family == PF_UNIX)
+ return aa_unix_sock_perm(op, request, sock);
return aa_sk_perm(op, request, sock->sk);
}
@@ -1239,6 +1375,8 @@ static int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock,
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
+ if (sock->sk->sk_family == PF_UNIX)
+ return aa_unix_opt_perm(op, request, sock, level, optname);
return aa_sk_perm(op, request, sock->sk);
}
@@ -1292,14 +1430,18 @@ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
#endif
-static struct aa_label *sk_peer_label(struct sock *sk)
+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 ctx->peer;
+ return aa_get_label(ctx->peer);
- return ERR_PTR(-ENOPROTOOPT);
+ if (sk->sk_family != PF_UNIX)
+ return ERR_PTR(-ENOPROTOOPT);
+
+ return label;
}
/**
@@ -1322,7 +1464,7 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,
struct aa_label *peer;
label = begin_current_label_crit_section();
- peer = sk_peer_label(sock->sk);
+ peer = sk_peer_get_label(sock->sk);
if (IS_ERR(peer)) {
error = PTR_ERR(peer);
goto done;
@@ -1333,7 +1475,7 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,
/* don't include terminating \0 in slen, it breaks some apps */
if (slen < 0) {
error = -ENOMEM;
- goto done;
+ goto done_put;
}
if (slen > len) {
error = -ERANGE;
@@ -1345,6 +1487,9 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,
done_len:
if (copy_to_sockptr(optlen, &slen, sizeof(slen)))
error = -EFAULT;
+
+done_put:
+ aa_put_label(peer);
done:
end_current_label_crit_section(label);
kfree(name);
@@ -1456,8 +1601,12 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = {
LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security),
LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security),
+ LSM_HOOK_INIT(unix_stream_connect, apparmor_unix_stream_connect),
+ LSM_HOOK_INIT(unix_may_send, apparmor_unix_may_send),
+
LSM_HOOK_INIT(socket_create, apparmor_socket_create),
LSM_HOOK_INIT(socket_post_create, apparmor_socket_post_create),
+ LSM_HOOK_INIT(socket_socketpair, apparmor_socket_socketpair),
LSM_HOOK_INIT(socket_bind, apparmor_socket_bind),
LSM_HOOK_INIT(socket_connect, apparmor_socket_connect),
LSM_HOOK_INIT(socket_listen, apparmor_socket_listen),