/* 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;
}