From aa42e338600ab6b5a1368dc5c4920974192adfd1 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 10 Jul 2018 22:18:48 -0700 Subject: [PATCH] kernel-patches: add v4.17-out-of-tree net compatibility patches Add kernel patches that will NEVER be sent upstream. These provide abi compatibility with the v2.x network and af_unix rules. The 4.17 network mediation pull request deliberately broke abi compatibility with the v2.x rules, and these are provided so that distros who shipped the v2.x compatible patches can provide new kernels on older releases that require v2.x network support. Signed-off-by: John Johansen --- ...o-provide-compatibility-with-v2.x-ne.patch | 249 ++++ .../0002-apparmor-af_unix-mediation.patch | 1191 +++++++++++++++++ ...-fix-use-after-free-in-sk_peer_label.patch | 57 + 3 files changed, 1497 insertions(+) create mode 100644 kernel-patches/v4.17/0001-apparmor-patch-to-provide-compatibility-with-v2.x-ne.patch create mode 100644 kernel-patches/v4.17/0002-apparmor-af_unix-mediation.patch create mode 100644 kernel-patches/v4.17/0003-apparmor-fix-use-after-free-in-sk_peer_label.patch diff --git a/kernel-patches/v4.17/0001-apparmor-patch-to-provide-compatibility-with-v2.x-ne.patch b/kernel-patches/v4.17/0001-apparmor-patch-to-provide-compatibility-with-v2.x-ne.patch new file mode 100644 index 000000000..f1123009d --- /dev/null +++ b/kernel-patches/v4.17/0001-apparmor-patch-to-provide-compatibility-with-v2.x-ne.patch @@ -0,0 +1,249 @@ +From 02e2bc1b330f7e15dba671547a256a6f900f6e5d Mon Sep 17 00:00:00 2001 +From: John Johansen +Date: Sun, 17 Jun 2018 03:56:25 -0700 +Subject: [PATCH 1/3] apparmor: patch to provide compatibility with v2.x net + rules + +The networking rules upstreamed in 4.17 have a deliberate abi break +with the older 2.x network rules. + +This patch provides compatibility with the older rules for those +still using an apparmor 2.x userspace and still want network rules +to work on a newer kernel. + +Signed-off-by: John Johansen +--- + security/apparmor/apparmorfs.c | 1 + + security/apparmor/include/apparmor.h | 2 +- + security/apparmor/include/net.h | 11 ++++++++ + security/apparmor/include/policy.h | 2 ++ + security/apparmor/net.c | 31 ++++++++++++++++----- + security/apparmor/policy.c | 1 + + security/apparmor/policy_unpack.c | 54 ++++++++++++++++++++++++++++++++++-- + 7 files changed, 92 insertions(+), 10 deletions(-) + +diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c +index 1fdcc7d5a977..32f0e660ffd0 100644 +--- a/security/apparmor/apparmorfs.c ++++ b/security/apparmor/apparmorfs.c +@@ -2272,6 +2272,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { + AA_SFS_DIR("domain", aa_sfs_entry_domain), + AA_SFS_DIR("file", aa_sfs_entry_file), + AA_SFS_DIR("network_v8", aa_sfs_entry_network), ++ AA_SFS_DIR("network", aa_sfs_entry_network_compat), + AA_SFS_DIR("mount", aa_sfs_entry_mount), + AA_SFS_DIR("namespaces", aa_sfs_entry_ns), + AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), +diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h +index 73d63b58d875..17d89f3badc6 100644 +--- a/security/apparmor/include/apparmor.h ++++ b/security/apparmor/include/apparmor.h +@@ -24,7 +24,7 @@ + #define AA_CLASS_UNKNOWN 1 + #define AA_CLASS_FILE 2 + #define AA_CLASS_CAP 3 +-#define AA_CLASS_DEPRECATED 4 ++#define AA_CLASS_NET_COMPAT 4 + #define AA_CLASS_RLIMITS 5 + #define AA_CLASS_DOMAIN 6 + #define AA_CLASS_MOUNT 7 +diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h +index ec7228e857a9..579b59a40ea4 100644 +--- a/security/apparmor/include/net.h ++++ b/security/apparmor/include/net.h +@@ -72,6 +72,16 @@ struct aa_sk_ctx { + DEFINE_AUDIT_NET(NAME, OP, SK, (SK)->sk_family, (SK)->sk_type, \ + (SK)->sk_protocol) + ++/* struct aa_net - network confinement data ++ * @allow: basic network families permissions ++ * @audit: which network permissions to force audit ++ * @quiet: which network permissions to quiet rejects ++ */ ++struct aa_net_compat { ++ u16 allow[AF_MAX]; ++ u16 audit[AF_MAX]; ++ u16 quiet[AF_MAX]; ++}; + + #define af_select(FAMILY, FN, DEF_FN) \ + ({ \ +@@ -84,6 +94,7 @@ struct aa_sk_ctx { + }) + + extern struct aa_sfs_entry aa_sfs_entry_network[]; ++extern struct aa_sfs_entry aa_sfs_entry_network_compat[]; + + void audit_net_cb(struct audit_buffer *ab, void *va); + int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa, +diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h +index 6c93e62383e6..4006fa9fc9f1 100644 +--- a/security/apparmor/include/policy.h ++++ b/security/apparmor/include/policy.h +@@ -112,6 +112,7 @@ struct aa_data { + * @policy: general match rules governing policy + * @file: The set of rules governing basic file access and domain transitions + * @caps: capabilities for the profile ++ * @net_compat: v2 compat network controls for the profile + * @rlimits: rlimits for the profile + * + * @dents: dentries for the profiles file entries in apparmorfs +@@ -149,6 +150,7 @@ struct aa_profile { + struct aa_policydb policy; + struct aa_file_rules file; + struct aa_caps caps; ++ struct aa_net_compat *net_compat; + + int xattr_count; + char **xattrs; +diff --git a/security/apparmor/net.c b/security/apparmor/net.c +index bb24cfa0a164..bf6aaefc3a5f 100644 +--- a/security/apparmor/net.c ++++ b/security/apparmor/net.c +@@ -27,6 +27,11 @@ 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), ++ { } ++}; ++ + static const char * const net_mask_names[] = { + "unknown", + "send", +@@ -119,14 +124,26 @@ int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa, + if (profile_unconfined(profile)) + return 0; + state = PROFILE_MEDIATES(profile, AA_CLASS_NET); +- if (!state) ++ if (state) { ++ if (!state) ++ return 0; ++ buffer[0] = cpu_to_be16(family); ++ buffer[1] = cpu_to_be16((u16) type); ++ state = aa_dfa_match_len(profile->policy.dfa, state, ++ (char *) &buffer, 4); ++ aa_compute_perms(profile->policy.dfa, state, &perms); ++ } else if (profile->net_compat) { ++ /* 2.x socket mediation compat */ ++ perms.allow = (profile->net_compat->allow[family] & (1 << type)) ? ++ ALL_PERMS_MASK : 0; ++ perms.audit = (profile->net_compat->audit[family] & (1 << type)) ? ++ ALL_PERMS_MASK : 0; ++ perms.quiet = (profile->net_compat->quiet[family] & (1 << type)) ? ++ ALL_PERMS_MASK : 0; ++ ++ } else { + return 0; +- +- buffer[0] = cpu_to_be16(family); +- buffer[1] = cpu_to_be16((u16) type); +- state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer, +- 4); +- 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); +diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c +index c07493ce2376..d1a869699040 100644 +--- a/security/apparmor/policy.c ++++ b/security/apparmor/policy.c +@@ -227,6 +227,7 @@ void aa_free_profile(struct aa_profile *profile) + aa_free_file_rules(&profile->file); + aa_free_cap_rules(&profile->caps); + aa_free_rlimit_rules(&profile->rlimits); ++ kzfree(profile->net_compat); + + for (i = 0; i < profile->xattr_count; i++) + kzfree(profile->xattrs[i]); +diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c +index b9e6b2cafa69..a1b07e6c163d 100644 +--- a/security/apparmor/policy_unpack.c ++++ b/security/apparmor/policy_unpack.c +@@ -37,7 +37,7 @@ + + #define v5 5 /* base version */ + #define v6 6 /* per entry policydb mediation check */ +-#define v7 7 ++#define v7 7 /* v2 compat networking */ + #define v8 8 /* full network masking */ + + /* +@@ -292,6 +292,19 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) + return 0; + } + ++static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) ++{ ++ if (unpack_nameX(e, AA_U16, name)) { ++ if (!inbounds(e, sizeof(u16))) ++ return 0; ++ if (data) ++ *data = le16_to_cpu(get_unaligned((__le16 *) e->pos)); ++ e->pos += sizeof(u16); ++ return 1; ++ } ++ return 0; ++} ++ + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) + { + if (unpack_nameX(e, AA_U32, name)) { +@@ -621,7 +634,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) + struct aa_profile *profile = NULL; + const char *tmpname, *tmpns = NULL, *name = NULL; + const char *info = "failed to unpack profile"; +- size_t ns_len; ++ size_t size = 0, ns_len; + struct rhashtable_params params = { 0 }; + char *key = NULL; + struct aa_data *data; +@@ -759,6 +772,43 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) + goto fail; + } + ++ size = unpack_array(e, "net_allowed_af"); ++ if (size || VERSION_LT(e->version, v8)) { ++ profile->net_compat = kzalloc(sizeof(struct aa_net_compat), GFP_KERNEL); ++ if (!profile->net_compat) { ++ info = "out of memory"; ++ goto fail; ++ } ++ for (i = 0; i < size; i++) { ++ /* discard extraneous rules that this kernel will ++ * never request ++ */ ++ if (i >= AF_MAX) { ++ u16 tmp; ++ ++ if (!unpack_u16(e, &tmp, NULL) || ++ !unpack_u16(e, &tmp, NULL) || ++ !unpack_u16(e, &tmp, NULL)) ++ goto fail; ++ continue; ++ } ++ if (!unpack_u16(e, &profile->net_compat->allow[i], NULL)) ++ goto fail; ++ if (!unpack_u16(e, &profile->net_compat->audit[i], NULL)) ++ goto fail; ++ if (!unpack_u16(e, &profile->net_compat->quiet[i], NULL)) ++ goto fail; ++ } ++ if (size && !unpack_nameX(e, AA_ARRAYEND, NULL)) ++ goto fail; ++ if (VERSION_LT(e->version, v7)) { ++ /* pre v7 policy always allowed these */ ++ profile->net_compat->allow[AF_UNIX] = 0xffff; ++ profile->net_compat->allow[AF_NETLINK] = 0xffff; ++ } ++ } ++ ++ + if (unpack_nameX(e, AA_STRUCT, "policydb")) { + /* generic policy dfa - optional and may be NULL */ + info = "failed to unpack policydb"; +-- +2.14.1 + diff --git a/kernel-patches/v4.17/0002-apparmor-af_unix-mediation.patch b/kernel-patches/v4.17/0002-apparmor-af_unix-mediation.patch new file mode 100644 index 000000000..75e0fc86f --- /dev/null +++ b/kernel-patches/v4.17/0002-apparmor-af_unix-mediation.patch @@ -0,0 +1,1191 @@ +From 1aae75e96831bb26d1ced782c633c39c877c252f Mon Sep 17 00:00:00 2001 +From: John Johansen +Date: Tue, 18 Jul 2017 23:27:23 -0700 +Subject: [PATCH 2/3] apparmor: af_unix mediation + +af_socket mediation did not make it into 4.17 so add remaining out +of tree patch + +Signed-off-by: John Johansen +--- + security/apparmor/Makefile | 3 +- + security/apparmor/af_unix.c | 652 ++++++++++++++++++++++++++++++++++++ + security/apparmor/apparmorfs.c | 6 + + security/apparmor/file.c | 4 +- + security/apparmor/include/af_unix.h | 114 +++++++ + security/apparmor/include/net.h | 4 + + security/apparmor/include/path.h | 1 + + security/apparmor/include/policy.h | 10 +- + security/apparmor/lsm.c | 113 +++++++ + security/apparmor/net.c | 53 ++- + security/apparmor/policy_unpack.c | 6 +- + 11 files changed, 957 insertions(+), 9 deletions(-) + create mode 100644 security/apparmor/af_unix.c + create mode 100644 security/apparmor/include/af_unix.h + +diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile +index ff23fcfefe19..fad407f6f62c 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 000000000000..54b3796f63d0 +--- /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 ++ ++#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 32f0e660ffd0..b931bae4f1a2 100644 +--- a/security/apparmor/apparmorfs.c ++++ b/security/apparmor/apparmorfs.c +@@ -2256,6 +2256,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), +@@ -2280,6 +2285,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 224b2fef93ca..67e70e094858 100644 +--- a/security/apparmor/file.c ++++ b/security/apparmor/file.c +@@ -16,6 +16,7 @@ + #include + #include + ++#include "include/af_unix.h" + #include "include/apparmor.h" + #include "include/audit.h" + #include "include/cred.h" +@@ -283,7 +284,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 000000000000..d1b7f2316be4 +--- /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 ++ ++#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(" "); ++ 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); : ""); print_sk(sock); \ ++ printk(" other %s:", other_ctx && other_ctx->label ? aa_label_printk(other_ctx->label, GFP_ATOMIC); : ""); print_sk(other); \ ++ printk(" new %s", new_ctx && new_ctx->label ? aa_label_printk(new_ctx->label, GFP_ATOMIC); : ""); 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 579b59a40ea4..48e07dcbb44d 100644 +--- a/security/apparmor/include/net.h ++++ b/security/apparmor/include/net.h +@@ -53,6 +53,7 @@ + struct aa_sk_ctx { + struct aa_label *label; + struct aa_label *peer; ++ struct path path; + }; + + #define SK_CTX(X) ((X)->sk_security) +@@ -87,6 +88,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 e042b994f2b8..29ab20eba812 100644 +--- a/security/apparmor/include/path.h ++++ b/security/apparmor/include/path.h +@@ -18,6 +18,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 4006fa9fc9f1..35da41f14056 100644 +--- a/security/apparmor/include/policy.h ++++ b/security/apparmor/include/policy.h +@@ -232,9 +232,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 ce2b89e9ad94..7a6b1bd8e046 100644 +--- a/security/apparmor/lsm.c ++++ b/security/apparmor/lsm.c +@@ -23,8 +23,10 @@ + #include + #include + #include ++#include + #include + ++#include "include/af_unix.h" + #include "include/apparmor.h" + #include "include/apparmorfs.h" + #include "include/audit.h" +@@ -776,6 +778,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); + } + +@@ -790,6 +793,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; + } + + /** +@@ -1027,11 +1123,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); + } + +@@ -1155,6 +1265,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 bf6aaefc3a5f..042aee4408c1 100644 +--- a/security/apparmor/net.c ++++ b/security/apparmor/net.c +@@ -12,6 +12,7 @@ + * License. + */ + ++#include "include/af_unix.h" + #include "include/apparmor.h" + #include "include/audit.h" + #include "include/cred.h" +@@ -29,6 +30,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), + { } + }; + +@@ -74,6 +76,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) +@@ -103,6 +135,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,5 +249,7 @@ 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)); + } +diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c +index a1b07e6c163d..9c9a329fd2d7 100644 +--- a/security/apparmor/policy_unpack.c ++++ b/security/apparmor/policy_unpack.c +@@ -296,13 +296,13 @@ static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) + { + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) +- return 0; ++ return false; + if (data) + *data = le16_to_cpu(get_unaligned((__le16 *) e->pos)); + e->pos += sizeof(u16); +- return 1; ++ return true; + } +- return 0; ++ return false; + } + + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) +-- +2.14.1 + diff --git a/kernel-patches/v4.17/0003-apparmor-fix-use-after-free-in-sk_peer_label.patch b/kernel-patches/v4.17/0003-apparmor-fix-use-after-free-in-sk_peer_label.patch new file mode 100644 index 000000000..ee37e5688 --- /dev/null +++ b/kernel-patches/v4.17/0003-apparmor-fix-use-after-free-in-sk_peer_label.patch @@ -0,0 +1,57 @@ +From 45ff74bd5a009ab8f9648531fa11fce55b9a67fd Mon Sep 17 00:00:00 2001 +From: John Johansen +Date: Tue, 26 Jun 2018 20:19:19 -0700 +Subject: [PATCH 3/3] apparmor: fix use after free in sk_peer_label + +BugLink: http://bugs.launchpad.net/bugs/1778646 +Signed-off-by: John Johansen +--- + security/apparmor/lsm.c | 11 +++++++---- + 1 file changed, 7 insertions(+), 4 deletions(-) + +diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c +index 7a6b1bd8e046..0d2925389947 100644 +--- a/security/apparmor/lsm.c ++++ b/security/apparmor/lsm.c +@@ -1125,9 +1125,10 @@ static struct aa_label *sk_peer_label(struct sock *sk) + { + struct sock *peer_sk; + struct aa_sk_ctx *ctx = SK_CTX(sk); ++ struct aa_label *label = ERR_PTR(-ENOPROTOOPT); + + if (ctx->peer) +- return ctx->peer; ++ return aa_get_label(ctx->peer); + + if (sk->sk_family != PF_UNIX) + return ERR_PTR(-ENOPROTOOPT); +@@ -1135,14 +1136,15 @@ static struct aa_label *sk_peer_label(struct sock *sk) + /* check for sockpair peering which does not go through + * security_unix_stream_connect + */ +- peer_sk = unix_peer(sk); ++ peer_sk = unix_peer_get(sk); + if (peer_sk) { + ctx = SK_CTX(peer_sk); + if (ctx->label) +- return ctx->label; ++ label = aa_get_label(ctx->label); ++ sock_put(peer_sk); + } + +- return ERR_PTR(-ENOPROTOOPT); ++ return label; + } + + /** +@@ -1186,6 +1188,7 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, + + } + ++ aa_put_label(peer); + done: + end_current_label_crit_section(label); + +-- +2.14.1 +