| /* SPDX-License-Identifier: GPL-2.0-only */ |
| /* |
| * AppArmor security module |
| * |
| * This file contains AppArmor inet fine grained mediation |
| * |
| * Copyright 2024 Canonical Ltd. |
| * |
| */ |
| |
| #include <net/tcp_states.h> |
| |
| #include "include/audit.h" |
| #include "include/af_inet.h" |
| #include "include/apparmor.h" |
| #include "include/file.h" |
| #include "include/label.h" |
| #include "include/net.h" |
| #include "include/path.h" |
| #include "include/policy.h" |
| #include "include/cred.h" |
| |
| |
| static inline aa_state_t RULE_MEDIATES_SK(struct aa_ruleset *rules, |
| const struct sock *sk) |
| { |
| return RULE_MEDIATES_NET(rules); |
| } |
| |
| |
| enum addr_type { |
| ADDR_LOCAL = 0, |
| ADDR_LOCAL_PRIV = 1, |
| ADDR_REMOTE = 2, |
| }; |
| |
| struct match_addr { |
| const char *addrp; |
| enum addr_type addrtype; |
| int len; |
| __be16 port; |
| }; |
| |
| struct stored_match_addr { |
| union { |
| struct sockaddr addr; |
| struct sockaddr_in addr4; |
| struct sockaddr_in6 addr6; |
| }; |
| int addrlen; |
| struct match_addr maddr; |
| }; |
| |
| static void set_ad_create(struct apparmor_audit_data *ad, |
| int family, int type, int protocol) |
| { |
| ad->common.u.net->family = family; |
| ad->net.type = type; |
| ad->net.protocol = protocol; |
| } |
| |
| static int set_ad_addr(struct apparmor_audit_data *ad, |
| u16 family, bool source, struct match_addr *maddr) |
| { |
| ad->common.u.net->family = family; |
| |
| if (source) { |
| ad->common.u.net->sport = maddr->port; |
| if (maddr->addrp) { |
| if (family == AF_INET) |
| /* ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; */ |
| ad->common.u.net->v4info.saddr = *(__be32 *)maddr->addrp; |
| else |
| /* ad.u.net->v4info.saddr = addr6->sin6_addr.s6_addr; */ |
| ad->common.u.net->v6info.saddr = *(struct in6_addr *)maddr->addrp; |
| } |
| } else { |
| ad->common.u.net->dport = maddr->port; |
| if (maddr->addrp) { |
| if (family == AF_INET) |
| /* ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; */ |
| ad->common.u.net->v4info.daddr = *(__be32 *)maddr->addrp; |
| else |
| /* ad.u.net->v4info.saddr = addr6->sin6_addr.s6_addr; */ |
| ad->common.u.net->v6info.daddr = *(struct in6_addr *)maddr->addrp; |
| } |
| } |
| return 0; |
| } |
| |
| /* returns 0 on success |
| * raw_port - if set raw_port (protocol) when SOCK_RAW */ |
| static int map_addr(struct sockaddr *addr, int addrlen, u16 raw_port, |
| enum addr_type addrtype, struct match_addr *maddr, |
| struct apparmor_audit_data *ad) |
| { |
| struct sockaddr_in *addr4 = NULL; |
| struct sockaddr_in6 *addr6 = NULL; |
| |
| AA_BUG(!addr); |
| AA_BUG(!maddr); |
| |
| maddr->addrtype = addrtype; |
| if (!addr || addrlen < offsetofend(struct sockaddr, sa_family)) { |
| maddr->addrp = NULL; |
| maddr->port = 0; |
| maddr->len = 0; |
| return 0; |
| } |
| |
| /* |
| * its possibly to have sk->sk_family == PF_INET6 and |
| * addr->sa_family == AF_INET. sk_family is used for socket |
| * mediation, sa_family for when we have address ... |
| */ |
| switch (addr->sa_family) { |
| case AF_INET: |
| addr4 = (struct sockaddr_in *)addr; |
| if (addrlen < sizeof(struct sockaddr_in)) |
| return -EINVAL; |
| maddr->port = addr4->sin_port; |
| maddr->addrp = (char *)&addr4->sin_addr.s_addr; |
| maddr->len = 4; |
| break; |
| case AF_INET6: |
| addr6 = (struct sockaddr_in6 *)addr; |
| if (addrlen < SIN6_LEN_RFC2133) |
| return -EINVAL; |
| maddr->port = addr6->sin6_port; |
| maddr->addrp = (char *)&addr6->sin6_addr.s6_addr; |
| maddr->len = 16; |
| break; |
| default: |
| return -EAFNOSUPPORT; |
| } |
| /* per ip spec, && sk->sk_type == SOCK_RAW*/ |
| if (raw_port && addrtype != ADDR_REMOTE) |
| maddr->port = htons(raw_port); |
| if (ad) |
| set_ad_addr(ad, addr->sa_family, addrtype != ADDR_REMOTE, maddr); |
| |
| return 0; |
| } |
| |
| /* -ENOTCONN if not connected */ |
| static int map_sock_addr(struct socket *sock, enum addr_type addrtype, |
| struct stored_match_addr *maddr, |
| struct apparmor_audit_data *ad) |
| { |
| /* do we need early bailout for !family ... */ |
| maddr->addrlen = sock->ops->getname(sock, (struct sockaddr *) &maddr->addr, addrtype != ADDR_REMOTE ? 0 : 1); |
| if (maddr->addrlen == -ENOTCONN) { |
| maddr->addrlen = 0; |
| return map_addr(NULL, 0, 0, addrtype, &maddr->maddr, ad); |
| } else if (maddr->addrlen < 0) |
| return maddr->addrlen; |
| return map_addr(&maddr->addr, maddr->addrlen, 0, addrtype, |
| &maddr->maddr, ad); |
| } |
| |
| /* TODO: combine with connect map addr */ |
| /* TODO: raw_port */ |
| static int bind_map_addr(const struct sock *sk, struct sockaddr *addr, |
| int addrlen, |
| struct match_addr *maddr, |
| struct apparmor_audit_data *ad) |
| { |
| struct sockaddr_in *addr4 = NULL; |
| struct sockaddr_in6 *addr6 = NULL; |
| u16 family; |
| |
| AA_BUG(!sk); |
| AA_BUG(!addr); |
| AA_BUG(!maddr); |
| |
| if (addrlen < offsetofend(struct sockaddr, sa_family)) |
| return -EINVAL; |
| |
| maddr->addrtype = ADDR_LOCAL; |
| /* |
| * its possibly to have sk->sk_family == PF_INET6 and |
| * addr->sa_family == AF_INET. sk_family is used for socket |
| * mediation, sa_family for when we have address ... |
| */ |
| family = addr->sa_family; |
| switch (addr->sa_family) { |
| case AF_UNSPEC: |
| if (sk->sk_family == PF_INET6) { |
| /* Length check from inet6_bind_sk() */ |
| if (addrlen < SIN6_LEN_RFC2133) |
| return -EINVAL; |
| /* Family check from __inet6_bind() */ |
| return -EAFNOSUPPORT; |
| } |
| /* see __inet_bind(), we only want to allow |
| * AF_UNSPEC if the address is INADDR_ANY |
| */ |
| addr4 = (struct sockaddr_in *)addr; |
| if (addr4->sin_addr.s_addr != htonl(INADDR_ANY)) |
| return -EAFNOSUPPORT; |
| family = AF_INET; |
| fallthrough; |
| case AF_INET: |
| addr4 = (struct sockaddr_in *)addr; |
| if (addrlen < sizeof(struct sockaddr_in)) |
| return -EINVAL; |
| maddr->port = addr4->sin_port; |
| maddr->addrp = (char *)&addr4->sin_addr.s_addr; |
| maddr->len = 4; |
| break; |
| case AF_INET6: |
| addr6 = (struct sockaddr_in6 *)addr; |
| if (addrlen < SIN6_LEN_RFC2133) |
| return -EINVAL; |
| maddr->port = addr6->sin6_port; |
| maddr->addrp = (char *)&addr6->sin6_addr.s6_addr; |
| maddr->len = 16; |
| break; |
| default: |
| return -EAFNOSUPPORT; |
| } |
| |
| if (ad) |
| set_ad_addr(ad, family, true, maddr); |
| |
| return 0; |
| } |
| |
| |
| static inline int profile_sk_perm(struct aa_profile *profile, u32 request, |
| const struct sock *sk, |
| struct match_addr *maddr, |
| struct apparmor_audit_data *ad) |
| { |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| |
| return aa_profile_af_sk_perm(profile, ad, request, sk); |
| } |
| |
| /* no kernel_t bailout */ |
| static int profile_create_perm(struct aa_profile *profile, int family, |
| int type, int protocol, |
| struct apparmor_audit_data *ad) |
| { |
| AA_BUG(!profile); |
| |
| return aa_profile_af_perm(profile, ad, AA_MAY_CREATE, family, type, |
| protocol); |
| } |
| |
| |
| /* sendmsg/rcvmsg/connect */ |
| static int profile_remote_perm(struct aa_profile *profile, |
| const struct sock *sk, |
| u32 request, struct match_addr *raddr, |
| struct match_addr *laddr, |
| struct apparmor_audit_data *ad) |
| { |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| AA_BUG(!raddr); |
| AA_BUG(!laddr); |
| AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, |
| "family=%d", sk->sk_family); |
| |
| return aa_profile_af_sk_perm(profile, ad, request, sk); |
| } |
| |
| static int profile_bind_perm(struct aa_profile *profile, |
| const struct sock *sk, |
| struct match_addr *maddr, |
| struct apparmor_audit_data *ad) |
| { |
| return aa_profile_af_sk_perm(profile, ad, AA_MAY_BIND, sk); |
| |
| } |
| |
| static int profile_listen_perm(struct aa_profile *profile, |
| const struct sock *sk, |
| struct match_addr *maddr, int backlog, |
| struct apparmor_audit_data *ad) |
| { |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| AA_BUG(!maddr); |
| AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, |
| "family=%d", sk->sk_family); |
| |
| return aa_profile_af_sk_perm(profile, ad, AA_MAY_LISTEN, sk); |
| } |
| |
| static inline int profile_accept_perm(struct aa_profile *profile, |
| const struct sock *sk, |
| struct match_addr *maddr, |
| const struct sock *newsk, |
| struct apparmor_audit_data *ad) |
| { |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| /* AA_BUG(!newsk); newsk can be null here, since not using atm ... */ |
| AA_BUG(!maddr); |
| AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, |
| "family=%d", sk->sk_family); |
| |
| return aa_profile_af_sk_perm(profile, ad, AA_MAY_ACCEPT, sk); |
| } |
| |
| /* getopt/setopt */ |
| static int profile_opt_perm(struct aa_profile *profile, u32 request, |
| const struct sock *sk, struct match_addr *maddr, |
| int level, int optname, |
| struct apparmor_audit_data *ad) |
| { |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| AA_BUG(!maddr); |
| AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, |
| "family=%d", sk->sk_family); |
| |
| return aa_profile_af_sk_perm(profile, ad, request, sk); |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| // TODO: cleanup init to use recursion, so we can have N init fns, in 1 macro |
| // TODO: lift DEFINE_AUDIT out of macro into init fn??? |
| |
| /* no kernel_t bailout */ |
| #define label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXX, YYYY, CALLBACKFN) \ |
| ({ \ |
| int __EERROR = 0; \ |
| if (label_mediates(LABEL, AA_CLASS_NET)) { \ |
| struct aa_profile *PROFILE; \ |
| DEFINE_AUDIT_SK(AAD, OP, CRED, SOCKSK); \ |
| (AAD).subj_cred = (CRED); \ |
| (AAD).request = (REQUEST); \ |
| __EERROR = (XXXX); \ |
| if (__EERROR == 0) { \ |
| __EERROR = (YYYY); \ |
| if (__EERROR == 0) { \ |
| __EERROR = fn_for_each(LABEL, PROFILE, \ |
| (CALLBACKFN)); \ |
| } \ |
| } \ |
| } \ |
| __EERROR; \ |
| }) |
| |
| /* no kernel_t bailout */ |
| #define label_sk_has_perm(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, CALLBACKFN) \ |
| label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, \ |
| 0, 0, CALLBACKFN) |
| |
| /* no kernel_t bailout */ |
| #define label_sk_has_perm1(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXX, CALLBACKFN) \ |
| label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, \ |
| XXXX, 0, CALLBACKFN) |
| |
| |
| /* Early bailout for kernel_t - 2 init args before callback */ |
| #define sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, YYYYX, CALLBACKFN) \ |
| ({ \ |
| struct aa_label *__label; \ |
| struct aa_sk_ctx *__ctx = aa_sock(SOCKSK); \ |
| int __ERROR = 0; \ |
| bool __needput; \ |
| if (rcu_access_pointer(__ctx->label) != kernel_t) { \ |
| \ |
| __label = begin_current_label_crit_section(&__needput); \ |
| __ERROR = label_sk_has_perm2(current_cred(), __label, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, YYYYX, CALLBACKFN); \ |
| end_current_label_crit_section(__label, __needput); \ |
| } \ |
| __ERROR; \ |
| }) |
| |
| /* Early bailout for kernel_t - no init args before callback */ |
| #define sk_has_perm(SOCKSK, OP, REQUEST, PROFILE, AAD, CALLBACKFN) \ |
| sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, 0, 0, CALLBACKFN) |
| |
| |
| /* Early bailout for kernel_t - 1 init arg before callback */ |
| #define sk_has_perm1(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, CALLBACKFN) \ |
| sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, 0, CALLBACKFN) |
| |
| |
| |
| /* no kernel_t early bailout */ |
| /* NOTE: already lifted label_mediates into lsm.c */ |
| int aa_inet_create_perm(struct aa_label *label, int family, int type, |
| int protocol) |
| { |
| struct aa_profile *profile; |
| int error = 0; |
| DEFINE_AUDIT_NET(ad, OP_CREATE, current_cred(), NULL, family, type, |
| protocol); |
| |
| ad.subj_cred = current_cred(); |
| set_ad_create(&ad, family, type, protocol); |
| error = fn_for_each(label, profile, |
| profile_create_perm(profile, family, type, |
| protocol, &ad)); |
| |
| return error; |
| } |
| |
| int aa_inet_bind_perm(struct socket *sock, struct sockaddr *addr, |
| int addrlen) |
| { |
| struct match_addr maddr; |
| |
| return sk_has_perm1(sock->sk, OP_BIND, AA_MAY_BIND, profile, ad, |
| bind_map_addr(sock->sk, addr, addrlen, &maddr, |
| &ad), |
| profile_bind_perm(profile, sock->sk, &maddr, &ad)); |
| } |
| |
| int aa_inet_connect_perm(struct socket *sock, struct sockaddr *addr, |
| int addrlen) |
| { |
| struct stored_match_addr laddr; |
| struct match_addr raddr; |
| |
| /* disconnect socket */ |
| if (addrlen < offsetofend(struct sockaddr, sa_family)) |
| return -EINVAL; |
| if (addr->sa_family == AF_UNSPEC) |
| return 0; |
| |
| /* do we need early bailout for !family ... */ |
| return sk_has_perm2(sock->sk, OP_CONNECT, AA_MAY_CONNECT, profile, ad, |
| map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad), |
| map_addr(addr, addrlen, 0, ADDR_REMOTE, &raddr, |
| &ad), |
| profile_remote_perm(profile, sock->sk, |
| AA_MAY_CONNECT, &raddr, |
| &laddr.maddr, &ad)); |
| } |
| |
| int aa_inet_listen_perm(struct socket *sock, int backlog) |
| { |
| struct stored_match_addr maddr; |
| |
| /* do we need early bailout for !family ... */ |
| return sk_has_perm1(sock->sk, OP_LISTEN, AA_MAY_LISTEN, profile, ad, |
| map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), |
| profile_listen_perm(profile, sock->sk, &maddr.maddr, |
| backlog, &ad)); |
| } |
| |
| /* ability of sock to connect, not peer address binding */ |
| int aa_inet_accept_perm(struct socket *sock, struct socket *newsock) |
| { |
| struct stored_match_addr maddr; |
| int error; |
| |
| error = sk_has_perm1(sock->sk, OP_ACCEPT, AA_MAY_ACCEPT, profile, ad, |
| map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), |
| profile_accept_perm(profile, sock->sk, |
| &maddr.maddr, |
| newsock->sk, &ad)); |
| |
| /* selinux updates inode - need to investigate this more */ |
| return error; |
| } |
| |
| /* sendmsg, recvmsg. */ |
| int aa_inet_msg_perm(const char *op, u32 request, struct socket *sock, |
| struct msghdr *msg, int size) |
| { |
| struct stored_match_addr laddr; |
| struct match_addr raddr; |
| |
| /* do we need early bailout for !family ... */ |
| return sk_has_perm2(sock->sk, op, request, profile, ad, |
| map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad), |
| map_addr(msg->msg_name, msg->msg_namelen, 0, |
| ADDR_REMOTE, &raddr, &ad), |
| profile_remote_perm(profile, sock->sk, request, |
| &raddr, &laddr.maddr, &ad)); |
| } |
| |
| /* getopt, setopt */ |
| int aa_inet_opt_perm(const char *op, u32 request, struct socket *sock, |
| int level, int optname) |
| { |
| struct stored_match_addr maddr; |
| |
| return sk_has_perm1(sock->sk, op, request, profile, ad, |
| map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), |
| profile_opt_perm(profile, request, sock->sk, |
| &maddr.maddr, level, optname, &ad)); |
| } |
| |
| static int inet_label_sock_perm(const struct cred *cred, struct aa_label *label, |
| const char *op, u32 request, |
| struct socket *sock) |
| { |
| struct stored_match_addr maddr; |
| |
| return label_sk_has_perm1(cred, label, sock->sk, op, request, profile, |
| ad, |
| map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), |
| profile_sk_perm(profile, request, sock->sk, |
| &maddr.maddr, &ad)); |
| } |
| |
| /* revalidation, get/set attr/getsockname/peername */ |
| int aa_inet_sock_perm(const char *op, u32 request, struct socket *sock) |
| { |
| struct aa_sk_ctx *ctx = aa_sock(sock->sk); |
| struct aa_label *label; |
| bool needput; |
| int error; |
| |
| if (rcu_access_pointer(ctx->label) == kernel_t) |
| return 0; |
| |
| label = begin_current_label_crit_section(&needput); |
| error = inet_label_sock_perm(current_cred(), label, op, request, sock); |
| end_current_label_crit_section(label, needput); |
| |
| return error; |
| } |
| |
| int aa_inet_file_perm(const struct cred *subj_cred, struct aa_label *label, |
| const char *op, u32 request, struct socket *sock) |
| { |
| u32 sk_req = request & ~NET_PEER_MASK; |
| struct stored_match_addr laddr; |
| const struct sock *sk = sock->sk; |
| int error = 0; |
| |
| AA_BUG(!label); |
| AA_BUG(!sock); |
| AA_BUG(!sock->sk); |
| AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, |
| "family=%d", sk->sk_family); |
| |
| /* access to the local sock */ |
| error = label_sk_has_perm1(subj_cred, label, sock->sk, op, request, |
| profile, ad, |
| map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad), |
| profile_sk_perm(profile, sk_req, sock->sk, &laddr.maddr, |
| &ad)); |
| |
| if (!error) { |
| struct stored_match_addr raddr; |
| |
| /* TODO: have ad here: instead of in CB so we do have to redo */ |
| error = map_sock_addr(sock, ADDR_REMOTE, &raddr, NULL); |
| if (!error && raddr.maddr.addrp) { |
| error = label_sk_has_perm1(subj_cred, label, sock->sk, |
| op, request, profile, ad, |
| set_ad_addr(&ad, raddr.addr.sa_family, |
| false, &raddr.maddr), |
| profile_remote_perm(profile, sock->sk, |
| request, |
| &raddr.maddr, |
| &laddr.maddr, &ad)); |
| } |
| } |
| |
| return error; |
| } |