mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
13 KiB
387 lines
13 KiB
#include <linux/kernel.h> |
|
#include <asm/uaccess.h> |
|
#include <asm/errno.h> |
|
#include <net/sock.h> |
|
#include <linux/file.h> |
|
#include <linux/fs.h> |
|
#include <linux/net.h> |
|
#include <linux/in.h> |
|
#include <linux/skbuff.h> |
|
#include <linux/ip.h> |
|
#include <linux/udp.h> |
|
#include <linux/types.h> |
|
#include <linux/sched.h> |
|
#include <linux/netdevice.h> |
|
#include <linux/inetdevice.h> |
|
#include <linux/gracl.h> |
|
#include <linux/grsecurity.h> |
|
#include <linux/grinternal.h> |
|
|
|
#define GR_BIND 0x01 |
|
#define GR_CONNECT 0x02 |
|
#define GR_INVERT 0x04 |
|
#define GR_BINDOVERRIDE 0x08 |
|
#define GR_CONNECTOVERRIDE 0x10 |
|
#define GR_SOCK_FAMILY 0x20 |
|
|
|
static const char * gr_protocols[IPPROTO_MAX] = { |
|
"ip", "icmp", "igmp", "ggp", "ipencap", "st", "tcp", "cbt", |
|
"egp", "igp", "bbn-rcc", "nvp", "pup", "argus", "emcon", "xnet", |
|
"chaos", "udp", "mux", "dcn", "hmp", "prm", "xns-idp", "trunk-1", |
|
"trunk-2", "leaf-1", "leaf-2", "rdp", "irtp", "iso-tp4", "netblt", "mfe-nsp", |
|
"merit-inp", "sep", "3pc", "idpr", "xtp", "ddp", "idpr-cmtp", "tp++", |
|
"il", "ipv6", "sdrp", "ipv6-route", "ipv6-frag", "idrp", "rsvp", "gre", |
|
"mhrp", "bna", "ipv6-crypt", "ipv6-auth", "i-nlsp", "swipe", "narp", "mobile", |
|
"tlsp", "skip", "ipv6-icmp", "ipv6-nonxt", "ipv6-opts", "unknown:61", "cftp", "unknown:63", |
|
"sat-expak", "kryptolan", "rvd", "ippc", "unknown:68", "sat-mon", "visa", "ipcv", |
|
"cpnx", "cphb", "wsn", "pvp", "br-sat-mon", "sun-nd", "wb-mon", "wb-expak", |
|
"iso-ip", "vmtp", "secure-vmtp", "vines", "ttp", "nfsnet-igp", "dgp", "tcf", |
|
"eigrp", "ospf", "sprite-rpc", "larp", "mtp", "ax.25", "ipip", "micp", |
|
"scc-sp", "etherip", "encap", "unknown:99", "gmtp", "ifmp", "pnni", "pim", |
|
"aris", "scps", "qnx", "a/n", "ipcomp", "snp", "compaq-peer", "ipx-in-ip", |
|
"vrrp", "pgm", "unknown:114", "l2tp", "ddx", "iatp", "stp", "srp", |
|
"uti", "smp", "sm", "ptp", "isis", "fire", "crtp", "crdup", |
|
"sscopmce", "iplt", "sps", "pipe", "sctp", "fc", "unkown:134", "unknown:135", |
|
"unknown:136", "unknown:137", "unknown:138", "unknown:139", "unknown:140", "unknown:141", "unknown:142", "unknown:143", |
|
"unknown:144", "unknown:145", "unknown:146", "unknown:147", "unknown:148", "unknown:149", "unknown:150", "unknown:151", |
|
"unknown:152", "unknown:153", "unknown:154", "unknown:155", "unknown:156", "unknown:157", "unknown:158", "unknown:159", |
|
"unknown:160", "unknown:161", "unknown:162", "unknown:163", "unknown:164", "unknown:165", "unknown:166", "unknown:167", |
|
"unknown:168", "unknown:169", "unknown:170", "unknown:171", "unknown:172", "unknown:173", "unknown:174", "unknown:175", |
|
"unknown:176", "unknown:177", "unknown:178", "unknown:179", "unknown:180", "unknown:181", "unknown:182", "unknown:183", |
|
"unknown:184", "unknown:185", "unknown:186", "unknown:187", "unknown:188", "unknown:189", "unknown:190", "unknown:191", |
|
"unknown:192", "unknown:193", "unknown:194", "unknown:195", "unknown:196", "unknown:197", "unknown:198", "unknown:199", |
|
"unknown:200", "unknown:201", "unknown:202", "unknown:203", "unknown:204", "unknown:205", "unknown:206", "unknown:207", |
|
"unknown:208", "unknown:209", "unknown:210", "unknown:211", "unknown:212", "unknown:213", "unknown:214", "unknown:215", |
|
"unknown:216", "unknown:217", "unknown:218", "unknown:219", "unknown:220", "unknown:221", "unknown:222", "unknown:223", |
|
"unknown:224", "unknown:225", "unknown:226", "unknown:227", "unknown:228", "unknown:229", "unknown:230", "unknown:231", |
|
"unknown:232", "unknown:233", "unknown:234", "unknown:235", "unknown:236", "unknown:237", "unknown:238", "unknown:239", |
|
"unknown:240", "unknown:241", "unknown:242", "unknown:243", "unknown:244", "unknown:245", "unknown:246", "unknown:247", |
|
"unknown:248", "unknown:249", "unknown:250", "unknown:251", "unknown:252", "unknown:253", "unknown:254", "unknown:255", |
|
}; |
|
|
|
static const char * gr_socktypes[SOCK_MAX] = { |
|
"unknown:0", "stream", "dgram", "raw", "rdm", "seqpacket", "unknown:6", |
|
"unknown:7", "unknown:8", "unknown:9", "packet" |
|
}; |
|
|
|
static const char * gr_sockfamilies[AF_MAX] = { |
|
"unspec", "unix", "inet", "ax25", "ipx", "appletalk", "netrom", "bridge", "atmpvc", "x25", |
|
"inet6", "rose", "decnet", "netbeui", "security", "key", "netlink", "packet", "ash", |
|
"econet", "atmsvc", "rds", "sna", "irda", "ppox", "wanpipe", "llc", "ib", "mpls", "can", |
|
"tipc", "bluetooth", "iucv", "rxrpc", "isdn", "phonet", "ieee802154", "ciaf", "alg", |
|
"nfc", "vsock", "kcm", "qipcrtr" |
|
}; |
|
|
|
const char * |
|
gr_proto_to_name(unsigned char proto) |
|
{ |
|
return gr_protocols[proto]; |
|
} |
|
|
|
const char * |
|
gr_socktype_to_name(unsigned char type) |
|
{ |
|
return gr_socktypes[type]; |
|
} |
|
|
|
const char * |
|
gr_sockfamily_to_name(unsigned char family) |
|
{ |
|
return gr_sockfamilies[family]; |
|
} |
|
|
|
extern const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly; |
|
|
|
int |
|
gr_search_socket(const int domain, const int type, const int protocol) |
|
{ |
|
struct acl_subject_label *curr; |
|
const struct cred *cred = current_cred(); |
|
|
|
if (unlikely(!gr_acl_is_enabled())) |
|
goto exit; |
|
|
|
if ((domain < 0) || (type < 0) || (protocol < 0) || |
|
(domain >= AF_MAX) || (type >= SOCK_MAX) || (protocol >= IPPROTO_MAX)) |
|
goto exit; // let the kernel handle it |
|
|
|
curr = current->acl; |
|
|
|
if (curr->sock_families[domain / 32] & (1U << (domain % 32))) { |
|
/* the family is allowed, if this is PF_INET allow it only if |
|
the extra sock type/protocol checks pass */ |
|
if (domain == PF_INET) |
|
goto inet_check; |
|
goto exit; |
|
} else { |
|
if (curr->mode & (GR_LEARN | GR_INHERITLEARN)) { |
|
__u32 fakeip = 0; |
|
security_learn(GR_IP_LEARN_MSG, current->role->rolename, |
|
current->role->roletype, GR_GLOBAL_UID(cred->uid), |
|
GR_GLOBAL_GID(cred->gid), current->exec_file ? |
|
gr_to_filename(current->exec_file->f_path.dentry, |
|
current->exec_file->f_path.mnt) : |
|
curr->filename, curr->filename, |
|
&fakeip, domain, 0, 0, GR_SOCK_FAMILY, |
|
¤t->signal->saved_ip); |
|
goto exit; |
|
} |
|
goto exit_fail; |
|
} |
|
|
|
inet_check: |
|
/* the rest of this checking is for IPv4 only */ |
|
if (!curr->ips) |
|
goto exit; |
|
|
|
if ((curr->ip_type & (1U << type)) && |
|
(curr->ip_proto[protocol / 32] & (1U << (protocol % 32)))) |
|
goto exit; |
|
|
|
if (curr->mode & (GR_LEARN | GR_INHERITLEARN)) { |
|
/* we don't place acls on raw sockets , and sometimes |
|
dgram/ip sockets are opened for ioctl and not |
|
bind/connect, so we'll fake a bind learn log */ |
|
if (type == SOCK_RAW || type == SOCK_PACKET) { |
|
__u32 fakeip = 0; |
|
security_learn(GR_IP_LEARN_MSG, current->role->rolename, |
|
current->role->roletype, GR_GLOBAL_UID(cred->uid), |
|
GR_GLOBAL_GID(cred->gid), current->exec_file ? |
|
gr_to_filename(current->exec_file->f_path.dentry, |
|
current->exec_file->f_path.mnt) : |
|
curr->filename, curr->filename, |
|
&fakeip, 0, type, |
|
protocol, GR_CONNECT, ¤t->signal->saved_ip); |
|
} else if ((type == SOCK_DGRAM) && (protocol == IPPROTO_IP)) { |
|
__u32 fakeip = 0; |
|
security_learn(GR_IP_LEARN_MSG, current->role->rolename, |
|
current->role->roletype, GR_GLOBAL_UID(cred->uid), |
|
GR_GLOBAL_GID(cred->gid), current->exec_file ? |
|
gr_to_filename(current->exec_file->f_path.dentry, |
|
current->exec_file->f_path.mnt) : |
|
curr->filename, curr->filename, |
|
&fakeip, 0, type, |
|
protocol, GR_BIND, ¤t->signal->saved_ip); |
|
} |
|
/* we'll log when they use connect or bind */ |
|
goto exit; |
|
} |
|
|
|
exit_fail: |
|
if (domain == PF_INET) |
|
gr_log_str3(GR_DONT_AUDIT, GR_SOCK_MSG, gr_sockfamily_to_name(domain), |
|
gr_socktype_to_name(type), gr_proto_to_name(protocol)); |
|
else if (rcu_access_pointer(net_families[domain]) != NULL) |
|
gr_log_str2_int(GR_DONT_AUDIT, GR_SOCK_NOINET_MSG, gr_sockfamily_to_name(domain), |
|
gr_socktype_to_name(type), protocol); |
|
|
|
return 0; |
|
exit: |
|
return 1; |
|
} |
|
|
|
int check_ip_policy(struct acl_ip_label *ip, __u32 ip_addr, __u16 ip_port, __u8 protocol, const int mode, const int type, __u32 our_addr, __u32 our_netmask) |
|
{ |
|
if ((ip->mode & mode) && |
|
(ip_port >= ip->low) && |
|
(ip_port <= ip->high) && |
|
((ntohl(ip_addr) & our_netmask) == |
|
(ntohl(our_addr) & our_netmask)) |
|
&& (ip->proto[protocol / 32] & (1U << (protocol % 32))) |
|
&& (ip->type & (1U << type))) { |
|
if (ip->mode & GR_INVERT) |
|
return 2; // specifically denied |
|
else |
|
return 1; // allowed |
|
} |
|
|
|
return 0; // not specifically allowed, may continue parsing |
|
} |
|
|
|
static int |
|
gr_search_connectbind(const int full_mode, struct sock *sk, |
|
struct sockaddr_in *addr, const int type) |
|
{ |
|
char iface[IFNAMSIZ] = {0}; |
|
struct acl_subject_label *curr; |
|
struct acl_ip_label *ip; |
|
struct inet_sock *isk; |
|
struct net_device *dev; |
|
struct in_device *idev; |
|
unsigned long i; |
|
int ret; |
|
int mode = full_mode & (GR_BIND | GR_CONNECT); |
|
__u32 ip_addr = 0; |
|
__u32 our_addr; |
|
__u32 our_netmask; |
|
char *p; |
|
__u16 ip_port = 0; |
|
const struct cred *cred = current_cred(); |
|
|
|
if (unlikely(!gr_acl_is_enabled() || sk->sk_family != PF_INET)) |
|
return 0; |
|
|
|
curr = current->acl; |
|
isk = inet_sk(sk); |
|
|
|
/* INADDR_ANY overriding for binds, inaddr_any_override is already in network order */ |
|
if ((full_mode & GR_BINDOVERRIDE) && addr->sin_addr.s_addr == htonl(INADDR_ANY) && curr->inaddr_any_override != 0) |
|
addr->sin_addr.s_addr = curr->inaddr_any_override; |
|
if ((full_mode & GR_CONNECT) && isk->inet_saddr == htonl(INADDR_ANY) && curr->inaddr_any_override != 0) { |
|
struct sockaddr_in saddr; |
|
int err; |
|
|
|
saddr.sin_family = AF_INET; |
|
saddr.sin_addr.s_addr = curr->inaddr_any_override; |
|
saddr.sin_port = isk->inet_sport; |
|
|
|
err = security_socket_bind(sk->sk_socket, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)); |
|
if (err) |
|
return err; |
|
|
|
err = sk->sk_socket->ops->bind(sk->sk_socket, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)); |
|
if (err) |
|
return err; |
|
} |
|
|
|
if (!curr->ips) |
|
return 0; |
|
|
|
ip_addr = addr->sin_addr.s_addr; |
|
ip_port = ntohs(addr->sin_port); |
|
|
|
if (curr->mode & (GR_LEARN | GR_INHERITLEARN)) { |
|
security_learn(GR_IP_LEARN_MSG, current->role->rolename, |
|
current->role->roletype, GR_GLOBAL_UID(cred->uid), |
|
GR_GLOBAL_GID(cred->gid), current->exec_file ? |
|
gr_to_filename(current->exec_file->f_path.dentry, |
|
current->exec_file->f_path.mnt) : |
|
curr->filename, curr->filename, |
|
&ip_addr, ip_port, type, |
|
sk->sk_protocol, mode, ¤t->signal->saved_ip); |
|
return 0; |
|
} |
|
|
|
for (i = 0; i < curr->ip_num; i++) { |
|
ip = *(curr->ips + i); |
|
if (ip->iface != NULL) { |
|
strncpy(iface, ip->iface, IFNAMSIZ - 1); |
|
p = strchr(iface, ':'); |
|
if (p != NULL) |
|
*p = '\0'; |
|
dev = dev_get_by_name(sock_net(sk), iface); |
|
if (dev == NULL) |
|
continue; |
|
idev = in_dev_get(dev); |
|
if (idev == NULL) { |
|
dev_put(dev); |
|
continue; |
|
} |
|
rcu_read_lock(); |
|
for_ifa(idev) { |
|
if (!strcmp(ip->iface, ifa->ifa_label)) { |
|
our_addr = ifa->ifa_address; |
|
our_netmask = 0xffffffff; |
|
ret = check_ip_policy(ip, ip_addr, ip_port, sk->sk_protocol, mode, type, our_addr, our_netmask); |
|
if (ret == 1) { |
|
rcu_read_unlock(); |
|
in_dev_put(idev); |
|
dev_put(dev); |
|
return 0; |
|
} else if (ret == 2) { |
|
rcu_read_unlock(); |
|
in_dev_put(idev); |
|
dev_put(dev); |
|
goto denied; |
|
} |
|
} |
|
} endfor_ifa(idev); |
|
rcu_read_unlock(); |
|
in_dev_put(idev); |
|
dev_put(dev); |
|
} else { |
|
our_addr = ip->addr; |
|
our_netmask = ip->netmask; |
|
ret = check_ip_policy(ip, ip_addr, ip_port, sk->sk_protocol, mode, type, our_addr, our_netmask); |
|
if (ret == 1) |
|
return 0; |
|
else if (ret == 2) |
|
goto denied; |
|
} |
|
} |
|
|
|
denied: |
|
if (mode == GR_BIND) |
|
gr_log_int5_str2(GR_DONT_AUDIT, GR_BIND_ACL_MSG, &ip_addr, ip_port, gr_socktype_to_name(type), gr_proto_to_name(sk->sk_protocol)); |
|
else if (mode == GR_CONNECT) |
|
gr_log_int5_str2(GR_DONT_AUDIT, GR_CONNECT_ACL_MSG, &ip_addr, ip_port, gr_socktype_to_name(type), gr_proto_to_name(sk->sk_protocol)); |
|
|
|
return -EACCES; |
|
} |
|
|
|
int |
|
gr_search_connect(struct socket *sock, struct sockaddr_in *addr) |
|
{ |
|
/* always allow disconnection of dgram sockets with connect */ |
|
if (addr->sin_family == AF_UNSPEC) |
|
return 0; |
|
return gr_search_connectbind(GR_CONNECT | GR_CONNECTOVERRIDE, sock->sk, addr, sock->type); |
|
} |
|
|
|
int |
|
gr_search_bind(struct socket *sock, struct sockaddr_in *addr) |
|
{ |
|
return gr_search_connectbind(GR_BIND | GR_BINDOVERRIDE, sock->sk, addr, sock->type); |
|
} |
|
|
|
int gr_search_listen(struct socket *sock) |
|
{ |
|
struct sock *sk = sock->sk; |
|
struct sockaddr_in addr; |
|
|
|
addr.sin_addr.s_addr = inet_sk(sk)->inet_saddr; |
|
addr.sin_port = inet_sk(sk)->inet_sport; |
|
|
|
return gr_search_connectbind(GR_BIND | GR_CONNECTOVERRIDE, sock->sk, &addr, sock->type); |
|
} |
|
|
|
int gr_search_accept(struct socket *sock) |
|
{ |
|
struct sock *sk = sock->sk; |
|
struct sockaddr_in addr; |
|
|
|
addr.sin_addr.s_addr = inet_sk(sk)->inet_saddr; |
|
addr.sin_port = inet_sk(sk)->inet_sport; |
|
|
|
return gr_search_connectbind(GR_BIND | GR_CONNECTOVERRIDE, sock->sk, &addr, sock->type); |
|
} |
|
|
|
int |
|
gr_search_udp_sendmsg(struct sock *sk, struct sockaddr_in *addr) |
|
{ |
|
if (addr) |
|
return gr_search_connectbind(GR_CONNECT, sk, addr, SOCK_DGRAM); |
|
else { |
|
struct sockaddr_in sin; |
|
const struct inet_sock *inet = inet_sk(sk); |
|
|
|
sin.sin_addr.s_addr = inet->inet_daddr; |
|
sin.sin_port = inet->inet_dport; |
|
|
|
return gr_search_connectbind(GR_CONNECT | GR_CONNECTOVERRIDE, sk, &sin, SOCK_DGRAM); |
|
} |
|
} |
|
|
|
int |
|
gr_search_udp_recvmsg(struct sock *sk, const struct sk_buff *skb) |
|
{ |
|
struct sockaddr_in sin; |
|
|
|
if (unlikely(skb->len < sizeof (struct udphdr))) |
|
return 0; // skip this packet |
|
|
|
sin.sin_addr.s_addr = ip_hdr(skb)->saddr; |
|
sin.sin_port = udp_hdr(skb)->source; |
|
|
|
return gr_search_connectbind(GR_CONNECT | GR_CONNECTOVERRIDE, sk, &sin, SOCK_DGRAM); |
|
}
|
|
|