diff options
author | Andrey Alekseev | 2020-09-06 16:32:00 +0400 |
---|---|---|
committer | Andrey Alekseev | 2020-09-06 16:32:00 +0400 |
commit | d0922d87e4ea90e985ff74a03d48bff11040ea22 (patch) | |
tree | b1dd50e9de23342cc12d2944265c131312d50c0e /0002-apparmor-af_unix-mediation.patch | |
download | aur-d0922d87e4ea90e985ff74a03d48bff11040ea22.tar.gz |
Initial import
Diffstat (limited to '0002-apparmor-af_unix-mediation.patch')
-rw-r--r-- | 0002-apparmor-af_unix-mediation.patch | 1141 |
1 files changed, 1141 insertions, 0 deletions
diff --git a/0002-apparmor-af_unix-mediation.patch b/0002-apparmor-af_unix-mediation.patch new file mode 100644 index 000000000000..6840208b369d --- /dev/null +++ b/0002-apparmor-af_unix-mediation.patch @@ -0,0 +1,1141 @@ +diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile +index ff23fcfefe196da8d7e09f18c9eb8a4c4a5c2c08..fad407f6f62c82527e256866392508edd635d6d0 100644 +--- a/security/apparmor/Makefile ++++ b/security/apparmor/Makefile +@@ -5,7 +5,8 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o + + apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \ + path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ +- resource.o secid.o file.o policy_ns.o label.o mount.o net.o ++ resource.o secid.o file.o policy_ns.o label.o mount.o net.o \ ++ af_unix.o + apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o + + clean-files := capability_names.h rlim_names.h net_names.h +diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c +new file mode 100644 +index 0000000000000000000000000000000000000000..54b3796f63d0d207f04397e9e3445db5c784d0bd +--- /dev/null ++++ b/security/apparmor/af_unix.c +@@ -0,0 +1,652 @@ ++/* ++ * AppArmor security module ++ * ++ * This file contains AppArmor af_unix fine grained mediation ++ * ++ * Copyright 2018 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/audit.h" ++#include "include/af_unix.h" ++#include "include/apparmor.h" ++#include "include/file.h" ++#include "include/label.h" ++#include "include/path.h" ++#include "include/policy.h" ++#include "include/cred.h" ++ ++static inline struct sock *aa_sock(struct unix_sock *u) ++{ ++ return &u->sk; ++} ++ ++static inline int unix_fs_perm(const char *op, u32 mask, struct aa_label *label, ++ struct unix_sock *u, int flags) ++{ ++ AA_BUG(!label); ++ AA_BUG(!u); ++ AA_BUG(!UNIX_FS(aa_sock(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 aa_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 ++ */ ++ struct aa_sk_ctx *ctx = SK_CTX(&u->sk); ++ if (ctx->path.dentry) ++ return aa_path_perm(op, label, &ctx->path, flags, mask, ++ &cond); ++ 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, ++ 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) ++{ ++ __be16 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)->peer = &peer->label; ++ ++ if (state) { ++ state = aa_dfa_match(profile->policy.dfa, state, ++ peer->base.hname); ++ 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; ++ DEFINE_AUDIT_NET(sa, OP_CREATE, NULL, family, type, protocol); ++ ++ AA_BUG(!profile); ++ AA_BUG(profile_unconfined(profile)); ++ ++ if ((state = PROFILE_MEDIATES_AF(profile, AF_UNIX))) { ++ 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, &sa, AA_MAY_CREATE, family, type); ++} ++ ++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, const char *op, ++ u32 request, struct sock *sk) ++{ ++ unsigned int state; ++ DEFINE_AUDIT_SK(sa, op, sk); ++ ++ 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) { ++ state = match_to_sk(profile, state, unix_sk(sk), ++ &aad(&sa)->info); ++ return do_perms(profile, state, request, &sa); ++ } ++ ++ return aa_profile_af_sk_perm(profile, &sa, request, sk); ++} ++ ++int aa_unix_label_sk_perm(struct aa_label *label, const char *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, const char *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(const char *op, u32 request, struct socket *sock) ++{ ++ struct aa_label *label; ++ int error; ++ ++ label = begin_current_label_crit_section(); ++ error = unix_label_sock_perm(label, op, request, sock); ++ end_current_label_crit_section(label); ++ ++ return error; ++} ++ ++static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, ++ struct sockaddr *addr, int addrlen) ++{ ++ unsigned int state; ++ DEFINE_AUDIT_SK(sa, OP_BIND, sk); ++ ++ 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 */ ++ 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_sk_perm(profile, &sa, AA_MAY_BIND, sk); ++} ++ ++int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address, ++ int addrlen) ++{ ++ struct aa_profile *profile; ++ struct aa_label *label; ++ int error = 0; ++ ++ label = begin_current_label_crit_section(); ++ /* fs bind is handled by mknod */ ++ if (!(unconfined(label) || unix_addr_fs(address, addrlen))) ++ error = fn_for_each_confined(label, profile, ++ profile_bind_perm(profile, sock->sk, address, ++ addrlen)); ++ end_current_label_crit_section(label); ++ ++ return error; ++} ++ ++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; ++ DEFINE_AUDIT_SK(sa, OP_LISTEN, sk); ++ ++ 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) { ++ __be16 b = cpu_to_be16(backlog); ++ ++ 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_sk_perm(profile, &sa, AA_MAY_LISTEN, sk); ++} ++ ++int aa_unix_listen_perm(struct socket *sock, int backlog) ++{ ++ struct aa_profile *profile; ++ struct aa_label *label; ++ int error = 0; ++ ++ label = begin_current_label_crit_section(); ++ if (!(unconfined(label) || UNIX_FS(sock->sk))) ++ error = fn_for_each_confined(label, profile, ++ profile_listen_perm(profile, sock->sk, ++ backlog)); ++ end_current_label_crit_section(label); ++ ++ return error; ++} ++ ++ ++static inline int profile_accept_perm(struct aa_profile *profile, ++ struct sock *sk, ++ struct sock *newsk) ++{ ++ unsigned int state; ++ DEFINE_AUDIT_SK(sa, OP_ACCEPT, sk); ++ ++ 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) { ++ 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_sk_perm(profile, &sa, AA_MAY_ACCEPT, 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; ++ int error = 0; ++ ++ label = begin_current_label_crit_section(); ++ if (!(unconfined(label) || UNIX_FS(sock->sk))) ++ error = fn_for_each_confined(label, profile, ++ profile_accept_perm(profile, sock->sk, ++ newsock->sk)); ++ end_current_label_crit_section(label); ++ ++ return error; ++} ++ ++ ++/* 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(const char *op, u32 request, struct socket *sock, ++ struct msghdr *msg, int size) ++{ ++ return 0; ++} ++ ++ ++static int profile_opt_perm(struct aa_profile *profile, const char *op, u32 request, ++ struct sock *sk, int level, int optname) ++{ ++ unsigned int state; ++ DEFINE_AUDIT_SK(sa, op, sk); ++ ++ 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) { ++ __be16 b = cpu_to_be16(optname); ++ ++ 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_sk_perm(profile, &sa, request, sk); ++} ++ ++int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level, ++ int optname) ++{ ++ struct aa_profile *profile; ++ struct aa_label *label; ++ int error = 0; ++ ++ label = begin_current_label_crit_section(); ++ if (!(unconfined(label) || UNIX_FS(sock->sk))) ++ error = fn_for_each_confined(label, profile, ++ profile_opt_perm(profile, op, request, ++ sock->sk, level, optname)); ++ end_current_label_crit_section(label); ++ ++ return error; ++} ++ ++/* null peer_label is allowed, in which case the peer_sk label is used */ ++static int profile_peer_perm(struct aa_profile *profile, const char *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_ctx *peer_ctx = SK_CTX(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_ctx->label; ++ return fn_for_each_in_ns(peer_label, peerp, ++ match_label(profile, peerp, state, request, ++ sa)); ++ } ++ ++ return aa_profile_af_sk_perm(profile, sa, request, sk); ++} ++ ++/** ++ * ++ * Requires: lock held on both @sk and @peer_sk ++ */ ++int aa_unix_peer_perm(struct aa_label *label, const char *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(aa_sock(peeru))) ++ return unix_fs_perm(op, request, label, peeru, 0); ++ else if (UNIX_FS(aa_sock(u))) ++ return unix_fs_perm(op, request, label, u, 0); ++ else { ++ struct aa_profile *profile; ++ DEFINE_AUDIT_SK(sa, op, sk); ++ 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, const char *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_ctx *pctx = SK_CTX(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(pctx->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; ++} +diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c +index a4ed6163c03737d90e64e50b4bc4c24c031e7799..5ce4d95d48fba4feb60075134cd8e66f395b8a62 100644 +--- a/security/apparmor/apparmorfs.c ++++ b/security/apparmor/apparmorfs.c +@@ -2249,6 +2249,11 @@ static struct aa_sfs_entry aa_sfs_entry_ns[] = { + { } + }; + ++static struct aa_sfs_entry aa_sfs_entry_dbus[] = { ++ AA_SFS_FILE_STRING("mask", "acquire send receive"), ++ { } ++}; ++ + static struct aa_sfs_entry aa_sfs_entry_query_label[] = { + AA_SFS_FILE_STRING("perms", "allow deny audit quiet"), + AA_SFS_FILE_BOOLEAN("data", 1), +@@ -2273,6 +2278,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { + AA_SFS_DIR("caps", aa_sfs_entry_caps), + AA_SFS_DIR("ptrace", aa_sfs_entry_ptrace), + AA_SFS_DIR("signal", aa_sfs_entry_signal), ++ AA_SFS_DIR("dbus", aa_sfs_entry_dbus), + AA_SFS_DIR("query", aa_sfs_entry_query), + { } + }; +diff --git a/security/apparmor/file.c b/security/apparmor/file.c +index 4c1b05eb130c32422c238089dd61c539037d30f8..8aa7af54b70af79c5cdd06d80de9cb9737348d29 100644 +--- a/security/apparmor/file.c ++++ b/security/apparmor/file.c +@@ -12,6 +12,7 @@ + #include <linux/fdtable.h> + #include <linux/file.h> + ++#include "include/af_unix.h" + #include "include/apparmor.h" + #include "include/audit.h" + #include "include/cred.h" +@@ -280,7 +281,8 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, + { + int e = 0; + +- if (profile_unconfined(profile)) ++ if (profile_unconfined(profile) || ++ ((flags & PATH_SOCK_COND) && !PROFILE_MEDIATES_AF(profile, AF_UNIX))) + return 0; + aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms); + if (request & ~perms->allow) +diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h +new file mode 100644 +index 0000000000000000000000000000000000000000..d1b7f2316be47d7631499ccbecc79e43c2a00c3b +--- /dev/null ++++ b/security/apparmor/include/af_unix.h +@@ -0,0 +1,114 @@ ++/* ++ * 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. ++ */ ++#ifndef __AA_AF_UNIX_H ++ ++#include <net/af_unix.h> ++ ++#include "label.h" ++//#include "include/net.h" ++ ++#define unix_addr_len(L) ((L) - sizeof(sa_family_t)) ++#define unix_abstract_name_len(L) (unix_addr_len(L) - 1) ++#define unix_abstract_len(U) (unix_abstract_name_len((U)->addr->len)) ++#define addr_unix_abstract_name(B) ((B)[0] == 0) ++#define addr_unix_anonymous(U) (addr_unix_len(U) <= 0) ++#define addr_unix_abstract(U) (!addr_unix_anonymous(U) && addr_unix_abstract_name((U)->addr)) ++//#define unix_addr_fs(U) (!unix_addr_anonymous(U) && !unix_addr_abstract_name((U)->addr)) ++ ++#define unix_addr(A) ((struct sockaddr_un *)(A)) ++#define unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0) ++#define unix_addr_fs(A, L) (!unix_addr_anon(A, L) && !addr_unix_abstract_name(unix_addr(A)->sun_path)) ++ ++#define UNIX_ANONYMOUS(U) (!unix_sk(U)->addr) ++/* from net/unix/af_unix.c */ ++#define UNIX_ABSTRACT(U) (!UNIX_ANONYMOUS(U) && \ ++ unix_sk(U)->addr->hash < UNIX_HASH_SIZE) ++#define UNIX_FS(U) (!UNIX_ANONYMOUS(U) && unix_sk(U)->addr->name->sun_path[0]) ++#define unix_peer(sk) (unix_sk(sk)->peer) ++#define unix_connected(S) ((S)->state == SS_CONNECTED) ++ ++static inline void print_unix_addr(struct sockaddr_un *A, int L) ++{ ++ char *buf = (A) ? (char *) &(A)->sun_path : NULL; ++ int len = unix_addr_len(L); ++ if (!buf || len <= 0) ++ printk(" <anonymous>"); ++ else if (buf[0]) ++ printk(" %s", buf); ++ else ++ /* abstract name len includes leading \0 */ ++ printk(" %d @%.*s", len - 1, len - 1, buf+1); ++}; ++ ++/* ++ printk("%s: %s: f %d, t %d, p %d", __FUNCTION__, \ ++ #SK , \ ++*/ ++#define print_unix_sk(SK) \ ++do { \ ++ struct unix_sock *u = unix_sk(SK); \ ++ printk("%s: f %d, t %d, p %d", #SK , \ ++ (SK)->sk_family, (SK)->sk_type, (SK)->sk_protocol); \ ++ if (u->addr) \ ++ print_unix_addr(u->addr->name, u->addr->len); \ ++ else \ ++ print_unix_addr(NULL, sizeof(sa_family_t)); \ ++ /* printk("\n");*/ \ ++} while (0) ++ ++#define print_sk(SK) \ ++do { \ ++ if (!(SK)) { \ ++ printk("%s: %s is null\n", __FUNCTION__, #SK); \ ++ } else if ((SK)->sk_family == PF_UNIX) { \ ++ print_unix_sk(SK); \ ++ printk("\n"); \ ++ } else { \ ++ printk("%s: %s: family %d\n", __FUNCTION__, #SK , \ ++ (SK)->sk_family); \ ++ } \ ++} while (0) ++ ++#define print_sock_addr(U) \ ++do { \ ++ printk("%s:\n", __FUNCTION__); \ ++ printk(" sock %s:", sock_ctx && sock_ctx->label ? aa_label_printk(sock_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(sock); \ ++ printk(" other %s:", other_ctx && other_ctx->label ? aa_label_printk(other_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(other); \ ++ printk(" new %s", new_ctx && new_ctx->label ? aa_label_printk(new_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(newsk); \ ++} while (0) ++ ++ ++ ++ ++int aa_unix_peer_perm(struct aa_label *label, const char *op, u32 request, ++ struct sock *sk, struct sock *peer_sk, ++ struct aa_label *peer_label); ++int aa_unix_label_sk_perm(struct aa_label *label, const char *op, u32 request, ++ struct sock *sk); ++int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock); ++int aa_unix_create_perm(struct aa_label *label, int family, int type, ++ int protocol); ++int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address, ++ int addrlen); ++int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address, ++ int addrlen); ++int aa_unix_listen_perm(struct socket *sock, int backlog); ++int aa_unix_accept_perm(struct socket *sock, struct socket *newsock); ++int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock, ++ struct msghdr *msg, int size); ++int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level, ++ int optname); ++int aa_unix_file_perm(struct aa_label *label, const char *op, u32 request, ++ struct socket *sock); ++ ++#endif /* __AA_AF_UNIX_H */ +diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h +index 74768db9406656cb5ac080cd23d3a4faafd9e1ea..4ff96fa0b0b501eacc7ce48250394c48316e5080 100644 +--- a/security/apparmor/include/net.h ++++ b/security/apparmor/include/net.h +@@ -49,6 +49,7 @@ + struct aa_sk_ctx { + struct aa_label *label; + struct aa_label *peer; ++ struct path path; + }; + + #define SK_CTX(X) ((X)->sk_security) +@@ -83,6 +84,9 @@ struct aa_net_compat { + ({ \ + int __e; \ + switch ((FAMILY)) { \ ++ case AF_UNIX: \ ++ __e = aa_unix_ ## FN; \ ++ break; \ + default: \ + __e = DEF_FN; \ + } \ +diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h +index 35a8295e8f3a96867d3c49f50f0de2131fca5661..de800033195361415fad1f2c28e09ccdfbeece58 100644 +--- a/security/apparmor/include/path.h ++++ b/security/apparmor/include/path.h +@@ -14,6 +14,7 @@ + + enum path_flags { + PATH_IS_DIR = 0x1, /* path is a directory */ ++ PATH_SOCK_COND = 0x2, + PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */ + PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */ + PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */ +diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h +index 6515a9174835c7d41279da92e6cafd72868ef181..a04d71803aeb53dacbb032d2a575db0a745153cb 100644 +--- a/security/apparmor/include/policy.h ++++ b/security/apparmor/include/policy.h +@@ -221,9 +221,13 @@ static inline unsigned int PROFILE_MEDIATES_AF(struct aa_profile *profile, + unsigned int state = PROFILE_MEDIATES(profile, AA_CLASS_NET); + __be16 be_af = cpu_to_be16(AF); + +- if (!state) +- return 0; +- return aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2); ++ if (!state) { ++ state = PROFILE_MEDIATES(profile, AA_CLASS_NET_COMPAT); ++ if (!state) ++ return 0; ++ } ++ state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2); ++ return state; + } + + /** +diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c +index ec3a928af8299a1ccca676575c9b469c977ae3e5..5c54d4588ede7be8a7d14469dec9129f9dafc406 100644 +--- a/security/apparmor/lsm.c ++++ b/security/apparmor/lsm.c +@@ -24,6 +24,7 @@ + #include <net/sock.h> + #include <uapi/linux/mount.h> + ++#include "include/af_unix.h" + #include "include/apparmor.h" + #include "include/apparmorfs.h" + #include "include/audit.h" +@@ -779,6 +780,7 @@ static void apparmor_sk_free_security(struct sock *sk) + SK_CTX(sk) = NULL; + aa_put_label(ctx->label); + aa_put_label(ctx->peer); ++ path_put(&ctx->path); + kfree(ctx); + } + +@@ -793,6 +795,99 @@ static void apparmor_sk_clone_security(const struct sock *sk, + + new->label = aa_get_label(ctx->label); + new->peer = aa_get_label(ctx->peer); ++ new->path = ctx->path; ++ path_get(&new->path); ++} ++ ++static struct path *UNIX_FS_CONN_PATH(struct sock *sk, struct sock *newsk) ++{ ++ if (sk->sk_family == PF_UNIX && UNIX_FS(sk)) ++ return &unix_sk(sk)->path; ++ else if (newsk->sk_family == PF_UNIX && UNIX_FS(newsk)) ++ return &unix_sk(newsk)->path; ++ return NULL; ++} ++ ++/** ++ * 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 = SK_CTX(sk); ++ struct aa_sk_ctx *peer_ctx = SK_CTX(peer_sk); ++ struct aa_sk_ctx *new_ctx = SK_CTX(newsk); ++ struct aa_label *label; ++ struct path *path; ++ int error; ++ ++ label = __begin_current_label_crit_section(); ++ error = aa_unix_peer_perm(label, OP_CONNECT, ++ (AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE), ++ sk, peer_sk, NULL); ++ if (!UNIX_FS(peer_sk)) { ++ last_error(error, ++ aa_unix_peer_perm(peer_ctx->label, OP_CONNECT, ++ (AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE), ++ peer_sk, sk, label)); ++ } ++ __end_current_label_crit_section(label); ++ ++ if (error) ++ return error; ++ ++ /* label newsk if it wasn't labeled in post_create. Normally this ++ * would be done in sock_graft, but because we are directly looking ++ * at the peer_sk to obtain peer_labeling for unix socks this ++ * does not work ++ */ ++ if (!new_ctx->label) ++ new_ctx->label = aa_get_label(peer_ctx->label); ++ ++ /* Cross reference the peer labels for SO_PEERSEC */ ++ if (new_ctx->peer) ++ aa_put_label(new_ctx->peer); ++ ++ if (sk_ctx->peer) ++ aa_put_label(sk_ctx->peer); ++ ++ new_ctx->peer = aa_get_label(sk_ctx->label); ++ sk_ctx->peer = aa_get_label(peer_ctx->label); ++ ++ path = UNIX_FS_CONN_PATH(sk, peer_sk); ++ if (path) { ++ new_ctx->path = *path; ++ sk_ctx->path = *path; ++ path_get(path); ++ path_get(path); ++ } ++ return 0; ++} ++ ++/** ++ * apparmor_unix_may_send - check perms before conn or sending unix dgrams ++ * ++ * other is locked when this hook is called ++ * ++ * dgram connect calls may_send, peer setup but path not copied????? ++ */ ++static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) ++{ ++ struct aa_sk_ctx *peer_ctx = SK_CTX(peer->sk); ++ struct aa_label *label; ++ int error; ++ ++ label = __begin_current_label_crit_section(); ++ error = xcheck(aa_unix_peer_perm(label, OP_SENDMSG, AA_MAY_SEND, ++ sock->sk, peer->sk, NULL), ++ aa_unix_peer_perm(peer_ctx->label, OP_SENDMSG, ++ AA_MAY_RECEIVE, ++ peer->sk, sock->sk, label)); ++ __end_current_label_crit_section(label); ++ ++ return error; + } + + /** +@@ -1038,11 +1133,25 @@ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) + + static struct aa_label *sk_peer_label(struct sock *sk) + { ++ struct sock *peer_sk; + struct aa_sk_ctx *ctx = SK_CTX(sk); + + if (ctx->peer) + return ctx->peer; + ++ if (sk->sk_family != PF_UNIX) ++ return ERR_PTR(-ENOPROTOOPT); ++ ++ /* check for sockpair peering which does not go through ++ * security_unix_stream_connect ++ */ ++ peer_sk = unix_peer(sk); ++ if (peer_sk) { ++ ctx = SK_CTX(peer_sk); ++ if (ctx->label) ++ return ctx->label; ++ } ++ + return ERR_PTR(-ENOPROTOOPT); + } + +@@ -1189,6 +1298,9 @@ static struct security_hook_list apparmor_hooks[] __lsm_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_bind, apparmor_socket_bind), +diff --git a/security/apparmor/net.c b/security/apparmor/net.c +index 1d8f5ff53cd4c19b73532387accb034589ef1122..297589c6dc95ec88b27b55ffac51a7ceb8ce07ff 100644 +--- a/security/apparmor/net.c ++++ b/security/apparmor/net.c +@@ -8,6 +8,7 @@ + * Copyright 2009-2017 Canonical Ltd. + */ + ++#include "include/af_unix.h" + #include "include/apparmor.h" + #include "include/audit.h" + #include "include/cred.h" +@@ -26,6 +27,7 @@ struct aa_sfs_entry aa_sfs_entry_network[] = { + + struct aa_sfs_entry aa_sfs_entry_network_compat[] = { + AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK), ++ AA_SFS_FILE_BOOLEAN("af_unix", 1), + { } + }; + +@@ -71,6 +73,36 @@ static const char * const net_mask_names[] = { + "unknown", + }; + ++static void audit_unix_addr(struct audit_buffer *ab, const char *str, ++ struct sockaddr_un *addr, int addrlen) ++{ ++ int len = unix_addr_len(addrlen); ++ ++ if (!addr || len <= 0) { ++ audit_log_format(ab, " %s=none", str); ++ } else if (addr->sun_path[0]) { ++ audit_log_format(ab, " %s=", str); ++ audit_log_untrustedstring(ab, addr->sun_path); ++ } else { ++ audit_log_format(ab, " %s=\"@", str); ++ if (audit_string_contains_control(&addr->sun_path[1], len - 1)) ++ audit_log_n_hex(ab, &addr->sun_path[1], len - 1); ++ else ++ audit_log_format(ab, "%.*s", len - 1, ++ &addr->sun_path[1]); ++ audit_log_format(ab, "\""); ++ } ++} ++ ++static void audit_unix_sk_addr(struct audit_buffer *ab, const char *str, ++ struct sock *sk) ++{ ++ struct unix_sock *u = unix_sk(sk); ++ if (u && u->addr) ++ audit_unix_addr(ab, str, u->addr->name, u->addr->len); ++ else ++ audit_unix_addr(ab, str, NULL, 0); ++} + + /* audit callback for net specific fields */ + void audit_net_cb(struct audit_buffer *ab, void *va) +@@ -100,6 +132,23 @@ void audit_net_cb(struct audit_buffer *ab, void *va) + net_mask_names, NET_PERMS_MASK); + } + } ++ if (sa->u.net->family == AF_UNIX) { ++ if ((aad(sa)->request & ~NET_PEER_MASK) && aad(sa)->net.addr) ++ audit_unix_addr(ab, "addr", ++ unix_addr(aad(sa)->net.addr), ++ aad(sa)->net.addrlen); ++ else ++ audit_unix_sk_addr(ab, "addr", sa->u.net->sk); ++ if (aad(sa)->request & NET_PEER_MASK) { ++ if (aad(sa)->net.addr) ++ audit_unix_addr(ab, "peer_addr", ++ unix_addr(aad(sa)->net.addr), ++ aad(sa)->net.addrlen); ++ else ++ audit_unix_sk_addr(ab, "peer_addr", ++ aad(sa)->net.peer_sk); ++ } ++ } + if (aad(sa)->peer) { + audit_log_format(ab, " peer="); + aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, +@@ -200,7 +249,9 @@ int aa_sock_file_perm(struct aa_label *label, const char *op, u32 request, + AA_BUG(!sock); + AA_BUG(!sock->sk); + +- return aa_label_sk_perm(label, op, request, sock->sk); ++ return af_select(sock->sk->sk_family, ++ file_perm(label, op, request, sock), ++ aa_label_sk_perm(label, op, request, sock->sk)); + } + + #ifdef CONFIG_NETWORK_SECMARK |