| /* |
| * AppArmor security module |
| * |
| * This file contains AppArmor af_unix fine grained mediation |
| * |
| * Copyright 2014 Canonical Ltd. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation, version 2 of the |
| * License. |
| */ |
| |
| #include <net/tcp_states.h> |
| |
| #include "include/af_unix.h" |
| #include "include/apparmor.h" |
| #include "include/context.h" |
| #include "include/file.h" |
| #include "include/label.h" |
| #include "include/path.h" |
| #include "include/policy.h" |
| |
| static inline int unix_fs_perm(int op, u32 mask, struct aa_label *label, |
| struct unix_sock *u, int flags) |
| { |
| AA_BUG(!label); |
| AA_BUG(!u); |
| AA_BUG(!UNIX_FS(u)); |
| |
| if (unconfined(label) || !LABEL_MEDIATES(label, AA_CLASS_FILE)) |
| return 0; |
| |
| mask &= NET_FS_PERMS; |
| if (!u->path.dentry) { |
| struct path_cond cond = { }; |
| struct file_perms perms = { }; |
| struct aa_profile *profile; |
| |
| /* socket path has been cleared because it is being shutdown |
| * can only fall back to original sun_path request |
| */ |
| return fn_for_each_confined(label, profile, |
| ((flags | profile->path_flags) & PATH_MEDIATE_DELETED) ? |
| __aa_path_perm(op, profile, |
| u->addr->name->sun_path, mask, |
| &cond, flags, &perms) : |
| aa_audit_file(profile, &nullperms, op, mask, |
| u->addr->name->sun_path, NULL, |
| cond.uid, "Failed name lookup - " |
| "deleted entry", -EACCES)); |
| } else { |
| /* the sunpath may not be valid for this ns so use the path */ |
| struct path_cond cond = { u->path.dentry->d_inode->i_uid, |
| u->path.dentry->d_inode->i_mode |
| }; |
| |
| return aa_path_perm(op, label, &u->path, flags, mask, &cond); |
| } |
| |
| return 0; |
| } |
| |
| /* passing in state returned by PROFILE_MEDIATES_AF */ |
| static unsigned int match_to_prot(struct aa_profile *profile, |
| unsigned int state, int type, int protocol, |
| const char **info) |
| { |
| u16 buffer[2]; |
| buffer[0] = cpu_to_be16(type); |
| buffer[1] = cpu_to_be16(protocol); |
| state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer, |
| 4); |
| if (!state) |
| *info = "failed type and protocol match"; |
| return state; |
| } |
| |
| static unsigned int match_addr(struct aa_profile *profile, unsigned int state, |
| struct sockaddr_un *addr, int addrlen) |
| { |
| if (addr) |
| /* include leading \0 */ |
| state = aa_dfa_match_len(profile->policy.dfa, state, |
| addr->sun_path, |
| unix_addr_len(addrlen)); |
| else |
| /* anonymous end point */ |
| state = aa_dfa_match_len(profile->policy.dfa, state, "\x01", |
| 1); |
| /* todo change to out of band */ |
| state = aa_dfa_null_transition(profile->policy.dfa, state); |
| return state; |
| } |
| |
| static unsigned int match_to_local(struct aa_profile *profile, |
| unsigned int state, int type, int protocol, |
| struct sockaddr_un *addr, int addrlen, |
| const char **info) |
| { |
| state = match_to_prot(profile, state, type, protocol, info); |
| if (state) { |
| state = match_addr(profile, state, addr, addrlen); |
| if (state) { |
| /* todo: local label matching */ |
| state = aa_dfa_null_transition(profile->policy.dfa, |
| state); |
| if (!state) |
| *info = "failed local label match"; |
| } else |
| *info = "failed local address match"; |
| } |
| |
| return state; |
| } |
| |
| static unsigned int match_to_sk(struct aa_profile *profile, |
| unsigned int state, struct unix_sock *u, |
| const char **info) |
| { |
| struct sockaddr_un *addr = NULL; |
| int addrlen = 0; |
| |
| if (u->addr) { |
| addr = u->addr->name; |
| addrlen = u->addr->len; |
| } |
| |
| return match_to_local(profile, state, u->sk.sk_type, u->sk.sk_protocol, |
| addr, addrlen, info); |
| } |
| |
| #define CMD_ADDR 1 |
| #define CMD_LISTEN 2 |
| #define CMD_OPT 4 |
| |
| static inline unsigned int match_to_cmd(struct aa_profile *profile, |
| unsigned int state, struct unix_sock *u, |
| char cmd, const char **info) |
| { |
| state = match_to_sk(profile, state, u, info); |
| if (state) { |
| state = aa_dfa_match_len(profile->policy.dfa, state, &cmd, 1); |
| if (!state) |
| *info = "failed cmd selection match"; |
| } |
| |
| return state; |
| } |
| |
| static inline unsigned int match_to_peer(struct aa_profile *profile, |
| unsigned int state, |
| struct unix_sock *u, |
| struct sockaddr_un *peer_addr, |
| int peer_addrlen, |
| const char **info) |
| { |
| state = match_to_cmd(profile, state, u, CMD_ADDR, info); |
| if (state) { |
| state = match_addr(profile, state, peer_addr, peer_addrlen); |
| if (!state) |
| *info = "failed peer address match"; |
| } |
| return state; |
| } |
| |
| static int do_perms(struct aa_profile *profile, unsigned int state, u32 request, |
| struct common_audit_data *sa) |
| { |
| struct aa_perms perms; |
| |
| AA_BUG(!profile); |
| |
| aa_compute_perms(profile->policy.dfa, state, &perms); |
| aa_apply_modes_to_perms(profile, &perms); |
| return aa_check_perms(profile, &perms, request, sa, |
| audit_net_cb); |
| } |
| |
| static int match_label(struct aa_profile *profile, struct aa_profile *peer, |
| unsigned int state, u32 request, |
| struct common_audit_data *sa) |
| { |
| AA_BUG(!profile); |
| AA_BUG(!peer); |
| |
| aad(sa)->target = aa_peer_name(peer); |
| |
| if (state) { |
| state = aa_dfa_match(profile->policy.dfa, state, aa_peer_name(peer)); |
| if (!state) |
| aad(sa)->info = "failed peer label match"; |
| } |
| return do_perms(profile, state, request, sa); |
| } |
| |
| |
| /* unix sock creation comes before we know if the socket will be an fs |
| * socket |
| * v6 - semantics are handled by mapping in profile load |
| * v7 - semantics require sock create for tasks creating an fs socket. |
| */ |
| static int profile_create_perm(struct aa_profile *profile, int family, |
| int type, int protocol) |
| { |
| unsigned int state; |
| |
| AA_BUG(!profile); |
| AA_BUG(profile_unconfined(profile)); |
| |
| if ((state = PROFILE_MEDIATES_AF(profile, AF_UNIX))) { |
| DEFINE_AUDIT_UNIX(sa, OP_CREATE, NULL, type, protocol); |
| |
| state = match_to_prot(profile, state, type, protocol, |
| &aad(&sa)->info); |
| return do_perms(profile, state, AA_MAY_CREATE, &sa); |
| } |
| |
| return aa_profile_af_perm(profile, OP_CREATE, family, type, protocol, |
| NULL); |
| } |
| |
| int aa_unix_create_perm(struct aa_label *label, int family, int type, |
| int protocol) |
| { |
| struct aa_profile *profile; |
| |
| if (unconfined(label)) |
| return 0; |
| |
| return fn_for_each_confined(label, profile, |
| profile_create_perm(profile, family, type, protocol)); |
| } |
| |
| |
| static inline int profile_sk_perm(struct aa_profile *profile, int op, |
| u32 request, struct sock *sk) |
| { |
| unsigned int state; |
| |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| AA_BUG(UNIX_FS(sk)); |
| AA_BUG(profile_unconfined(profile)); |
| |
| state = PROFILE_MEDIATES_AF(profile, AF_UNIX); |
| if (state) { |
| DEFINE_AUDIT_UNIX(sa, op, sk, sk->sk_type, sk->sk_protocol); |
| |
| state = match_to_sk(profile, state, unix_sk(sk), |
| &aad(&sa)->info); |
| return do_perms(profile, state, request, &sa); |
| } |
| |
| return aa_profile_af_perm(profile, op, sk->sk_family, sk->sk_type, |
| sk->sk_protocol, sk); |
| } |
| |
| int aa_unix_label_sk_perm(struct aa_label *label, int op, u32 request, |
| struct sock *sk) |
| { |
| struct aa_profile *profile; |
| |
| return fn_for_each_confined(label, profile, |
| profile_sk_perm(profile, op, request, sk)); |
| } |
| |
| static int unix_label_sock_perm(struct aa_label *label, int op, u32 request, |
| struct socket *sock) |
| { |
| if (unconfined(label)) |
| return 0; |
| if (UNIX_FS(sock->sk)) |
| return unix_fs_perm(op, request, label, unix_sk(sock->sk), 0); |
| |
| return aa_unix_label_sk_perm(label, op, request, sock->sk); |
| } |
| |
| /* revaliation, get/set attr */ |
| int aa_unix_sock_perm(int op, u32 request, struct socket *sock) |
| { |
| return unix_label_sock_perm(aa_current_label(), op, request, sock); |
| } |
| |
| static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, |
| struct sockaddr *addr, int addrlen) |
| { |
| unsigned int state; |
| |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| AA_BUG(addr->sa_family != AF_UNIX); |
| AA_BUG(profile_unconfined(profile)); |
| AA_BUG(unix_addr_fs(addr, addrlen)); |
| |
| state = PROFILE_MEDIATES_AF(profile, AF_UNIX); |
| if (state) { |
| /* bind for abstract socket */ |
| DEFINE_AUDIT_UNIX(sa, OP_BIND, sk, sk->sk_type, |
| sk->sk_protocol); |
| aad(&sa)->net.addr = unix_addr(addr); |
| aad(&sa)->net.addrlen = addrlen; |
| |
| state = match_to_local(profile, state, |
| sk->sk_type, sk->sk_protocol, |
| unix_addr(addr), addrlen, |
| &aad(&sa)->info); |
| return do_perms(profile, state, AA_MAY_BIND, &sa); |
| } |
| |
| return aa_profile_af_perm(profile, OP_BIND, sk->sk_family, sk->sk_type, |
| sk->sk_protocol, sk); |
| } |
| |
| int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address, |
| int addrlen) |
| { |
| struct aa_profile *profile; |
| struct aa_label *label = aa_current_label(); |
| |
| /* fs bind is handled by mknod */ |
| if (unconfined(label) || unix_addr_fs(address, addrlen)) |
| return 0; |
| |
| return fn_for_each_confined(label, profile, |
| profile_bind_perm(profile, sock->sk, address, addrlen)); |
| } |
| |
| int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address, |
| int addrlen) |
| { |
| /* unix connections are covered by the |
| * - unix_stream_connect (stream) and unix_may_send hooks (dgram) |
| * - fs connect is handled by open |
| */ |
| return 0; |
| } |
| |
| static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, |
| int backlog) |
| { |
| unsigned int state; |
| |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| AA_BUG(UNIX_FS(sk)); |
| AA_BUG(profile_unconfined(profile)); |
| |
| state = PROFILE_MEDIATES_AF(profile, AF_UNIX); |
| if (state) { |
| u16 b = cpu_to_be16(backlog); |
| DEFINE_AUDIT_UNIX(sa, OP_LISTEN, sk, sk->sk_type, |
| sk->sk_protocol); |
| |
| state = match_to_cmd(profile, state, unix_sk(sk), CMD_LISTEN, |
| &aad(&sa)->info); |
| if (state) { |
| state = aa_dfa_match_len(profile->policy.dfa, state, |
| (char *) &b, 2); |
| if (!state) |
| aad(&sa)->info = "failed listen backlog match"; |
| } |
| return do_perms(profile, state, AA_MAY_LISTEN, &sa); |
| } |
| |
| return aa_profile_af_perm(profile, OP_LISTEN, sk->sk_family, |
| sk->sk_type, sk->sk_protocol, sk); |
| } |
| |
| int aa_unix_listen_perm(struct socket *sock, int backlog) |
| { |
| struct aa_profile *profile; |
| struct aa_label *label = aa_current_label(); |
| |
| if (unconfined(label) || UNIX_FS(sock->sk)) |
| return 0; |
| |
| return fn_for_each_confined(label, profile, |
| profile_listen_perm(profile, sock->sk, backlog)); |
| } |
| |
| |
| static inline int profile_accept_perm(struct aa_profile *profile, |
| struct sock *sk, |
| struct sock *newsk) |
| { |
| unsigned int state; |
| |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| AA_BUG(UNIX_FS(sk)); |
| AA_BUG(profile_unconfined(profile)); |
| |
| state = PROFILE_MEDIATES_AF(profile, AF_UNIX); |
| if (state) { |
| DEFINE_AUDIT_UNIX(sa, OP_ACCEPT, sk, sk->sk_type, |
| sk->sk_protocol); |
| |
| state = match_to_sk(profile, state, unix_sk(sk), |
| &aad(&sa)->info); |
| return do_perms(profile, state, AA_MAY_ACCEPT, &sa); |
| } |
| |
| return aa_profile_af_perm(profile, OP_ACCEPT, sk->sk_family, |
| sk->sk_type, sk->sk_protocol, sk); |
| } |
| |
| /* ability of sock to connect, not peer address binding */ |
| int aa_unix_accept_perm(struct socket *sock, struct socket *newsock) |
| { |
| struct aa_profile *profile; |
| struct aa_label *label = aa_current_label(); |
| |
| if (unconfined(label) || UNIX_FS(sock->sk)) |
| return 0; |
| |
| return fn_for_each_confined(label, profile, |
| profile_accept_perm(profile, sock->sk, newsock->sk)); |
| } |
| |
| |
| /* dgram handled by unix_may_sendmsg, right to send on stream done at connect |
| * could do per msg unix_stream here |
| */ |
| /* sendmsg, recvmsg */ |
| int aa_unix_msg_perm(int op, u32 request, struct socket *sock, |
| struct msghdr *msg, int size) |
| { |
| return 0; |
| } |
| |
| |
| static int profile_opt_perm(struct aa_profile *profile, int op, u32 request, |
| struct sock *sk, int level, int optname) |
| { |
| unsigned int state; |
| |
| AA_BUG(!profile); |
| AA_BUG(!sk); |
| AA_BUG(UNIX_FS(sk)); |
| AA_BUG(profile_unconfined(profile)); |
| |
| state = PROFILE_MEDIATES_AF(profile, AF_UNIX); |
| if (state) { |
| u16 b = cpu_to_be16(optname); |
| DEFINE_AUDIT_UNIX(sa, op, sk, sk->sk_type, sk->sk_protocol); |
| |
| state = match_to_cmd(profile, state, unix_sk(sk), CMD_OPT, |
| &aad(&sa)->info); |
| if (state) { |
| state = aa_dfa_match_len(profile->policy.dfa, state, |
| (char *) &b, 2); |
| if (!state) |
| aad(&sa)->info = "failed sockopt match"; |
| } |
| return do_perms(profile, state, request, &sa); |
| } |
| |
| return aa_profile_af_perm(profile, op, sk->sk_family, |
| sk->sk_type, sk->sk_protocol, sk); |
| } |
| |
| int aa_unix_opt_perm(int op, u32 request, struct socket *sock, int level, |
| int optname) |
| { |
| struct aa_profile *profile; |
| struct aa_label *label = aa_current_label(); |
| |
| if (unconfined(label) || UNIX_FS(sock->sk)) |
| return 0; |
| |
| return fn_for_each_confined(label, profile, |
| profile_opt_perm(profile, op, request, sock->sk, |
| level, optname)); |
| } |
| |
| /* null peer_label is allowed, in which case the peer_sk label is used */ |
| static int profile_peer_perm(struct aa_profile *profile, int op, u32 request, |
| struct sock *sk, struct sock *peer_sk, |
| struct aa_label *peer_label, |
| struct common_audit_data *sa) |
| { |
| unsigned int state; |
| |
| AA_BUG(!profile); |
| AA_BUG(profile_unconfined(profile)); |
| AA_BUG(!sk); |
| AA_BUG(!peer_sk); |
| AA_BUG(UNIX_FS(peer_sk)); |
| |
| state = PROFILE_MEDIATES_AF(profile, AF_UNIX); |
| if (state) { |
| struct aa_sk_cxt *peer_cxt = SK_CXT(peer_sk); |
| struct aa_profile *peerp; |
| struct sockaddr_un *addr = NULL; |
| int len = 0; |
| if (unix_sk(peer_sk)->addr) { |
| addr = unix_sk(peer_sk)->addr->name; |
| len = unix_sk(peer_sk)->addr->len; |
| } |
| state = match_to_peer(profile, state, unix_sk(sk), |
| addr, len, &aad(sa)->info); |
| if (!peer_label) |
| peer_label = peer_cxt->label; |
| return fn_for_each(peer_label, peerp, |
| match_label(profile, peerp, state, request, |
| sa)); |
| } |
| |
| return aa_profile_af_perm(profile, op, sk->sk_family, sk->sk_type, |
| sk->sk_protocol, sk); |
| } |
| |
| /** |
| * |
| * Requires: lock held on both @sk and @peer_sk |
| */ |
| int aa_unix_peer_perm(struct aa_label *label, int op, u32 request, |
| struct sock *sk, struct sock *peer_sk, |
| struct aa_label *peer_label) |
| { |
| struct unix_sock *peeru = unix_sk(peer_sk); |
| struct unix_sock *u = unix_sk(sk); |
| |
| AA_BUG(!label); |
| AA_BUG(!sk); |
| AA_BUG(!peer_sk); |
| |
| if (UNIX_FS(peeru)) |
| return unix_fs_perm(op, request, label, peeru, 0); |
| else if (UNIX_FS(u)) |
| return unix_fs_perm(op, request, label, u, 0); |
| else { |
| struct aa_profile *profile; |
| DEFINE_AUDIT_UNIX(sa, op, sk, sk->sk_type, sk->sk_protocol); |
| aad(&sa)->net.peer_sk = peer_sk; |
| |
| /* TODO: ns!!! */ |
| if (!net_eq(sock_net(sk), sock_net(peer_sk))) { |
| ; |
| } |
| |
| if (unconfined(label)) |
| return 0; |
| |
| return fn_for_each_confined(label, profile, |
| profile_peer_perm(profile, op, request, sk, |
| peer_sk, peer_label, &sa)); |
| } |
| } |
| |
| |
| /* from net/unix/af_unix.c */ |
| static void unix_state_double_lock(struct sock *sk1, struct sock *sk2) |
| { |
| if (unlikely(sk1 == sk2) || !sk2) { |
| unix_state_lock(sk1); |
| return; |
| } |
| if (sk1 < sk2) { |
| unix_state_lock(sk1); |
| unix_state_lock_nested(sk2); |
| } else { |
| unix_state_lock(sk2); |
| unix_state_lock_nested(sk1); |
| } |
| } |
| |
| static void unix_state_double_unlock(struct sock *sk1, struct sock *sk2) |
| { |
| if (unlikely(sk1 == sk2) || !sk2) { |
| unix_state_unlock(sk1); |
| return; |
| } |
| unix_state_unlock(sk1); |
| unix_state_unlock(sk2); |
| } |
| |
| int aa_unix_file_perm(struct aa_label *label, int op, u32 request, |
| struct socket *sock) |
| { |
| struct sock *peer_sk = NULL; |
| u32 sk_req = request & ~NET_PEER_MASK; |
| int error = 0; |
| |
| AA_BUG(!label); |
| AA_BUG(!sock); |
| AA_BUG(!sock->sk); |
| AA_BUG(sock->sk->sk_family != AF_UNIX); |
| |
| /* TODO: update sock label with new task label */ |
| unix_state_lock(sock->sk); |
| peer_sk = unix_peer(sock->sk); |
| if (peer_sk) |
| sock_hold(peer_sk); |
| if (!unix_connected(sock) && sk_req) { |
| error = unix_label_sock_perm(label, op, sk_req, sock); |
| if (!error) { |
| // update label |
| } |
| } |
| unix_state_unlock(sock->sk); |
| if (!peer_sk) |
| return error; |
| |
| unix_state_double_lock(sock->sk, peer_sk); |
| if (UNIX_FS(sock->sk)) { |
| error = unix_fs_perm(op, request, label, unix_sk(sock->sk), |
| PATH_SOCK_COND); |
| } else if (UNIX_FS(peer_sk)) { |
| error = unix_fs_perm(op, request, label, unix_sk(peer_sk), |
| PATH_SOCK_COND); |
| } else { |
| struct aa_sk_cxt *pcxt = SK_CXT(peer_sk); |
| if (sk_req) |
| error = aa_unix_label_sk_perm(label, op, sk_req, |
| sock->sk); |
| last_error(error, |
| xcheck(aa_unix_peer_perm(label, op, |
| MAY_READ | MAY_WRITE, |
| sock->sk, peer_sk, NULL), |
| aa_unix_peer_perm(pcxt->label, op, |
| MAY_READ | MAY_WRITE, |
| peer_sk, sock->sk, label))); |
| } |
| |
| unix_state_double_unlock(sock->sk, peer_sk); |
| sock_put(peer_sk); |
| |
| return error; |
| } |