diff --git a/kernel-patches/v4.11/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch b/kernel-patches/v4.11/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch new file mode 100644 index 000000000..8a896a7d9 --- /dev/null +++ b/kernel-patches/v4.11/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch @@ -0,0 +1,605 @@ +From 97b3200925ba627346432edf521d49de8bb018a3 Mon Sep 17 00:00:00 2001 +From: John Johansen +Date: Mon, 4 Oct 2010 15:03:36 -0700 +Subject: [PATCH 1/3] UBUNTU: SAUCE: AppArmor: basic networking rules + +Base support for network mediation. + +Signed-off-by: John Johansen +--- + security/apparmor/.gitignore | 1 + + security/apparmor/Makefile | 42 ++++++++++- + security/apparmor/apparmorfs.c | 1 + + security/apparmor/include/audit.h | 4 + + security/apparmor/include/net.h | 59 +++++++++++++++ + security/apparmor/include/policy.h | 3 + + security/apparmor/lsm.c | 112 ++++++++++++++++++++++++++++ + security/apparmor/net.c | 148 +++++++++++++++++++++++++++++++++++++ + security/apparmor/policy.c | 1 + + security/apparmor/policy_unpack.c | 47 +++++++++++- + 10 files changed, 415 insertions(+), 3 deletions(-) + create mode 100644 security/apparmor/include/net.h + create mode 100644 security/apparmor/net.c + +diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore +index 9cdec70d72b8..d5b291e94264 100644 +--- a/security/apparmor/.gitignore ++++ b/security/apparmor/.gitignore +@@ -1,5 +1,6 @@ + # + # Generated include files + # ++net_names.h + capability_names.h + rlim_names.h +diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile +index ad369a7aac24..a7dc10be232d 100644 +--- a/security/apparmor/Makefile ++++ b/security/apparmor/Makefile +@@ -4,10 +4,10 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o + + apparmor-y := apparmorfs.o audit.o capability.o context.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 ++ resource.o secid.o file.o policy_ns.o net.o + apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o + +-clean-files := capability_names.h rlim_names.h ++clean-files := capability_names.h rlim_names.h net_names.h + + + # Build a lower case string table of capability names +@@ -25,6 +25,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ + -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \ + tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + ++# Build a lower case string table of address family names ++# Transform lines from ++# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ ++# #define AF_INET 2 /* Internet IP Protocol */ ++# to ++# [1] = "local", ++# [2] = "inet", ++# ++# and build the securityfs entries for the mapping. ++# Transforms lines from ++# #define AF_INET 2 /* Internet IP Protocol */ ++# to ++# #define AA_FS_AF_MASK "local inet" ++quiet_cmd_make-af = GEN $@ ++cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ ++ sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ ++ 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ ++ echo "};" >> $@ ;\ ++ echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ ++ sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ ++ $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ ++ ++# Build a lower case string table of sock type names ++# Transform lines from ++# SOCK_STREAM = 1, ++# to ++# [1] = "stream", ++quiet_cmd_make-sock = GEN $@ ++cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ ++ sed $^ >>$@ -r -n \ ++ -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ ++ echo "};" >> $@ + + # Build a lower case string table of rlimit names. + # Transforms lines from +@@ -61,6 +93,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ + tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + + $(obj)/capability.o : $(obj)/capability_names.h ++$(obj)/net.o : $(obj)/net_names.h + $(obj)/resource.o : $(obj)/rlim_names.h + $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ + $(src)/Makefile +@@ -68,3 +101,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ + $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ + $(src)/Makefile + $(call cmd,make-rlim) ++$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ ++ $(srctree)/include/linux/net.h \ ++ $(src)/Makefile ++ $(call cmd,make-af) ++ $(call cmd,make-sock) +diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c +index 41073f70eb41..4d236736cfb8 100644 +--- a/security/apparmor/apparmorfs.c ++++ b/security/apparmor/apparmorfs.c +@@ -1209,6 +1209,7 @@ static struct aa_fs_entry aa_fs_entry_features[] = { + AA_FS_DIR("policy", aa_fs_entry_policy), + AA_FS_DIR("domain", aa_fs_entry_domain), + AA_FS_DIR("file", aa_fs_entry_file), ++ AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), + AA_FS_DIR("rlimit", aa_fs_entry_rlimit), + AA_FS_DIR("caps", aa_fs_entry_caps), +diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h +index fdc4774318ba..0df708e8748b 100644 +--- a/security/apparmor/include/audit.h ++++ b/security/apparmor/include/audit.h +@@ -127,6 +127,10 @@ struct apparmor_audit_data { + int rlim; + unsigned long max; + } rlim; ++ struct { ++ int type, protocol; ++ struct sock *sk; ++ } net; + }; + }; + +diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h +new file mode 100644 +index 000000000000..55da1dad8720 +--- /dev/null ++++ b/security/apparmor/include/net.h +@@ -0,0 +1,59 @@ ++/* ++ * AppArmor security module ++ * ++ * This file contains AppArmor network mediation definitions. ++ * ++ * Copyright (C) 1998-2008 Novell/SUSE ++ * Copyright 2009-2012 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_NET_H ++#define __AA_NET_H ++ ++#include ++ ++#include "apparmorfs.h" ++ ++/* struct aa_net - network confinement data ++ * @allowed: basic network families permissions ++ * @audit_network: which network permissions to force audit ++ * @quiet_network: which network permissions to quiet rejects ++ */ ++struct aa_net { ++ u16 allow[AF_MAX]; ++ u16 audit[AF_MAX]; ++ u16 quiet[AF_MAX]; ++}; ++ ++extern struct aa_fs_entry aa_fs_entry_network[]; ++ ++#define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \ ++ struct lsm_network_audit NAME ## _net = { .sk = (SK), \ ++ .family = (F)}; \ ++ DEFINE_AUDIT_DATA(NAME, \ ++ ((SK) && (F) != AF_UNIX) ? LSM_AUDIT_DATA_NET : \ ++ LSM_AUDIT_DATA_NONE, \ ++ OP); \ ++ NAME.u.net = &(NAME ## _net); \ ++ aad(&NAME)->net.type = (T); \ ++ aad(&NAME)->net.protocol = (P) ++ ++#define DEFINE_AUDIT_SK(NAME, OP, SK) \ ++ DEFINE_AUDIT_NET(NAME, OP, SK, (SK)->sk_family, (SK)->sk_type, \ ++ (SK)->sk_protocol) ++ ++extern int aa_net_perm(const char *op, struct aa_profile *profile, u16 family, ++ int type, int protocol, struct sock *sk); ++extern int aa_revalidate_sk(const char *op, struct sock *sk); ++ ++static inline void aa_free_net_rules(struct aa_net *new) ++{ ++ /* NOP */ ++} ++ ++#endif /* __AA_NET_H */ +diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h +index 67bc96afe541..a3d18ea8d730 100644 +--- a/security/apparmor/include/policy.h ++++ b/security/apparmor/include/policy.h +@@ -28,6 +28,7 @@ + #include "capability.h" + #include "domain.h" + #include "file.h" ++#include "net.h" + #include "lib.h" + #include "resource.h" + +@@ -132,6 +133,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: network controls for the profile + * @rlimits: rlimits for the profile + * + * @dents: dentries for the profiles file entries in apparmorfs +@@ -174,6 +176,7 @@ struct aa_profile { + struct aa_policydb policy; + struct aa_file_rules file; + struct aa_caps caps; ++ struct aa_net net; + struct aa_rlimit rlimits; + + struct aa_loaddata *rawdata; +diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c +index 709eacd23909..e3017129a404 100644 +--- a/security/apparmor/lsm.c ++++ b/security/apparmor/lsm.c +@@ -33,6 +33,7 @@ + #include "include/context.h" + #include "include/file.h" + #include "include/ipc.h" ++#include "include/net.h" + #include "include/path.h" + #include "include/policy.h" + #include "include/policy_ns.h" +@@ -587,6 +588,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, + return error; + } + ++static int apparmor_socket_create(int family, int type, int protocol, int kern) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ if (kern) ++ return 0; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) ++ error = aa_net_perm(OP_CREATE, profile, family, type, protocol, ++ NULL); ++ return error; ++} ++ ++static int apparmor_socket_bind(struct socket *sock, ++ struct sockaddr *address, int addrlen) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_BIND, sk); ++} ++ ++static int apparmor_socket_connect(struct socket *sock, ++ struct sockaddr *address, int addrlen) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_CONNECT, sk); ++} ++ ++static int apparmor_socket_listen(struct socket *sock, int backlog) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_LISTEN, sk); ++} ++ ++static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_ACCEPT, sk); ++} ++ ++static int apparmor_socket_sendmsg(struct socket *sock, ++ struct msghdr *msg, int size) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_SENDMSG, sk); ++} ++ ++static int apparmor_socket_recvmsg(struct socket *sock, ++ struct msghdr *msg, int size, int flags) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_RECVMSG, sk); ++} ++ ++static int apparmor_socket_getsockname(struct socket *sock) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_GETSOCKNAME, sk); ++} ++ ++static int apparmor_socket_getpeername(struct socket *sock) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_GETPEERNAME, sk); ++} ++ ++static int apparmor_socket_getsockopt(struct socket *sock, int level, ++ int optname) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_GETSOCKOPT, sk); ++} ++ ++static int apparmor_socket_setsockopt(struct socket *sock, int level, ++ int optname) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_SETSOCKOPT, sk); ++} ++ ++static int apparmor_socket_shutdown(struct socket *sock, int how) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_SHUTDOWN, sk); ++} ++ + static struct security_hook_list apparmor_hooks[] = { + LSM_HOOK_INIT(ptrace_access_check, apparmor_ptrace_access_check), + LSM_HOOK_INIT(ptrace_traceme, apparmor_ptrace_traceme), +@@ -616,6 +715,19 @@ static struct security_hook_list apparmor_hooks[] = { + LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), + LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), + ++ LSM_HOOK_INIT(socket_create, apparmor_socket_create), ++ LSM_HOOK_INIT(socket_bind, apparmor_socket_bind), ++ LSM_HOOK_INIT(socket_connect, apparmor_socket_connect), ++ LSM_HOOK_INIT(socket_listen, apparmor_socket_listen), ++ LSM_HOOK_INIT(socket_accept, apparmor_socket_accept), ++ LSM_HOOK_INIT(socket_sendmsg, apparmor_socket_sendmsg), ++ LSM_HOOK_INIT(socket_recvmsg, apparmor_socket_recvmsg), ++ LSM_HOOK_INIT(socket_getsockname, apparmor_socket_getsockname), ++ LSM_HOOK_INIT(socket_getpeername, apparmor_socket_getpeername), ++ LSM_HOOK_INIT(socket_getsockopt, apparmor_socket_getsockopt), ++ LSM_HOOK_INIT(socket_setsockopt, apparmor_socket_setsockopt), ++ LSM_HOOK_INIT(socket_shutdown, apparmor_socket_shutdown), ++ + LSM_HOOK_INIT(cred_alloc_blank, apparmor_cred_alloc_blank), + LSM_HOOK_INIT(cred_free, apparmor_cred_free), + LSM_HOOK_INIT(cred_prepare, apparmor_cred_prepare), +diff --git a/security/apparmor/net.c b/security/apparmor/net.c +new file mode 100644 +index 000000000000..b9c8cd0e882e +--- /dev/null ++++ b/security/apparmor/net.c +@@ -0,0 +1,148 @@ ++/* ++ * AppArmor security module ++ * ++ * This file contains AppArmor network mediation ++ * ++ * Copyright (C) 1998-2008 Novell/SUSE ++ * Copyright 2009-2012 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/apparmor.h" ++#include "include/audit.h" ++#include "include/context.h" ++#include "include/net.h" ++#include "include/policy.h" ++ ++#include "net_names.h" ++ ++struct aa_fs_entry aa_fs_entry_network[] = { ++ AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), ++ { } ++}; ++ ++/* audit callback for net specific fields */ ++static void audit_cb(struct audit_buffer *ab, void *va) ++{ ++ struct common_audit_data *sa = va; ++ ++ audit_log_format(ab, " family="); ++ if (address_family_names[sa->u.net->family]) { ++ audit_log_string(ab, address_family_names[sa->u.net->family]); ++ } else { ++ audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); ++ } ++ audit_log_format(ab, " sock_type="); ++ if (sock_type_names[aad(sa)->net.type]) { ++ audit_log_string(ab, sock_type_names[aad(sa)->net.type]); ++ } else { ++ audit_log_format(ab, "\"unknown(%d)\"", aad(sa)->net.type); ++ } ++ audit_log_format(ab, " protocol=%d", aad(sa)->net.protocol); ++} ++ ++/** ++ * audit_net - audit network access ++ * @profile: profile being enforced (NOT NULL) ++ * @op: operation being checked ++ * @family: network family ++ * @type: network type ++ * @protocol: network protocol ++ * @sk: socket auditing is being applied to ++ * @error: error code for failure else 0 ++ * ++ * Returns: %0 or sa->error else other errorcode on failure ++ */ ++static int audit_net(struct aa_profile *profile, const char *op, u16 family, ++ int type, int protocol, struct sock *sk, int error) ++{ ++ int audit_type = AUDIT_APPARMOR_AUTO; ++ DEFINE_AUDIT_NET(sa, op, sk, family, type, protocol); ++ ++ aad(&sa)->error = error; ++ ++ if (likely(!aad(&sa)->error)) { ++ u16 audit_mask = profile->net.audit[sa.u.net->family]; ++ if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && ++ !(1 << aad(&sa)->net.type & audit_mask))) ++ return 0; ++ audit_type = AUDIT_APPARMOR_AUDIT; ++ } else { ++ u16 quiet_mask = profile->net.quiet[sa.u.net->family]; ++ u16 kill_mask = 0; ++ u16 denied = (1 << aad(&sa)->net.type) & ~quiet_mask; ++ ++ if (denied & kill_mask) ++ audit_type = AUDIT_APPARMOR_KILL; ++ ++ if ((denied & quiet_mask) && ++ AUDIT_MODE(profile) != AUDIT_NOQUIET && ++ AUDIT_MODE(profile) != AUDIT_ALL) ++ return COMPLAIN_MODE(profile) ? 0 : aad(&sa)->error; ++ } ++ ++ return aa_audit(audit_type, profile, &sa, audit_cb); ++} ++ ++/** ++ * aa_net_perm - very course network access check ++ * @op: operation being checked ++ * @profile: profile being enforced (NOT NULL) ++ * @family: network family ++ * @type: network type ++ * @protocol: network protocol ++ * ++ * Returns: %0 else error if permission denied ++ */ ++int aa_net_perm(const char *op, struct aa_profile *profile, u16 family, ++ int type, int protocol, struct sock *sk) ++{ ++ u16 family_mask; ++ int error; ++ ++ if ((family < 0) || (family >= AF_MAX)) ++ return -EINVAL; ++ ++ if ((type < 0) || (type >= SOCK_MAX)) ++ return -EINVAL; ++ ++ /* unix domain and netlink sockets are handled by ipc */ ++ if (family == AF_UNIX || family == AF_NETLINK) ++ return 0; ++ ++ family_mask = profile->net.allow[family]; ++ ++ error = (family_mask & (1 << type)) ? 0 : -EACCES; ++ ++ return audit_net(profile, op, family, type, protocol, sk, error); ++} ++ ++/** ++ * aa_revalidate_sk - Revalidate access to a sock ++ * @op: operation being checked ++ * @sk: sock being revalidated (NOT NULL) ++ * ++ * Returns: %0 else error if permission denied ++ */ ++int aa_revalidate_sk(const char *op, struct sock *sk) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ /* aa_revalidate_sk should not be called from interrupt context ++ * don't mediate these calls as they are not task related ++ */ ++ if (in_interrupt()) ++ return 0; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) ++ error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, ++ sk->sk_protocol, sk); ++ ++ return error; ++} +diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c +index def1fbd6bdfd..9fe7b9d4500f 100644 +--- a/security/apparmor/policy.c ++++ b/security/apparmor/policy.c +@@ -237,6 +237,7 @@ void aa_free_profile(struct aa_profile *profile) + + aa_free_file_rules(&profile->file); + aa_free_cap_rules(&profile->caps); ++ aa_free_net_rules(&profile->net); + aa_free_rlimit_rules(&profile->rlimits); + + kzfree(profile->dirname); +diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c +index 2e37c9c26bbd..bc23a5b3b113 100644 +--- a/security/apparmor/policy_unpack.c ++++ b/security/apparmor/policy_unpack.c +@@ -217,6 +217,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((u16 *) 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)) { +@@ -519,7 +532,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; +- size_t ns_len; ++ size_t ns_len, size = 0; + struct rhashtable_params params = { 0 }; + char *key = NULL; + struct aa_data *data; +@@ -635,6 +648,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) + if (!unpack_rlimits(e, profile)) + goto fail; + ++ size = unpack_array(e, "net_allowed_af"); ++ if (size) { ++ ++ 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.allow[i], NULL)) ++ goto fail; ++ if (!unpack_u16(e, &profile->net.audit[i], NULL)) ++ goto fail; ++ if (!unpack_u16(e, &profile->net.quiet[i], NULL)) ++ goto fail; ++ } ++ if (!unpack_nameX(e, AA_ARRAYEND, NULL)) ++ goto fail; ++ } ++ /* ++ * allow unix domain and netlink sockets they are handled ++ * by IPC ++ */ ++ profile->net.allow[AF_UNIX] = 0xffff; ++ profile->net.allow[AF_NETLINK] = 0xffff; ++ + if (unpack_nameX(e, AA_STRUCT, "policydb")) { + /* generic policy dfa - optional and may be NULL */ + profile->policy.dfa = unpack_dfa(e); +-- +2.11.0 + diff --git a/kernel-patches/v4.11/0002-apparmor-Fix-quieting-of-audit-messages-for-network-.patch b/kernel-patches/v4.11/0002-apparmor-Fix-quieting-of-audit-messages-for-network-.patch new file mode 100644 index 000000000..f1962d371 --- /dev/null +++ b/kernel-patches/v4.11/0002-apparmor-Fix-quieting-of-audit-messages-for-network-.patch @@ -0,0 +1,38 @@ +From b866a43c2897f5469c9d787426144074a3713f6a Mon Sep 17 00:00:00 2001 +From: John Johansen +Date: Fri, 29 Jun 2012 17:34:00 -0700 +Subject: [PATCH 2/3] apparmor: Fix quieting of audit messages for network + mediation + +If a profile specified a quieting of network denials for a given rule by +either the quiet or deny rule qualifiers, the resultant quiet mask for +denied requests was applied incorrectly, resulting in two potential bugs. +1. The misapplied quiet mask would prevent denials from being correctly + tested against the kill mask/mode. Thus network access requests that + should have resulted in the application being killed did not. + +2. The actual quieting of the denied network request was not being applied. + This would result in network rejections always being logged even when + they had been specifically marked as quieted. + +Signed-off-by: John Johansen +--- + security/apparmor/net.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/security/apparmor/net.c b/security/apparmor/net.c +index b9c8cd0e882e..5ba19ad1d65c 100644 +--- a/security/apparmor/net.c ++++ b/security/apparmor/net.c +@@ -74,7 +74,7 @@ static int audit_net(struct aa_profile *profile, const char *op, u16 family, + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; +- u16 denied = (1 << aad(&sa)->net.type) & ~quiet_mask; ++ u16 denied = (1 << aad(&sa)->net.type); + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; +-- +2.11.0 + diff --git a/kernel-patches/v4.11/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch b/kernel-patches/v4.11/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch new file mode 100644 index 000000000..f8fff6fc7 --- /dev/null +++ b/kernel-patches/v4.11/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch @@ -0,0 +1,938 @@ +From 4429c3f9522b608300cfe1ae148dc6cdadf3d76c Mon Sep 17 00:00:00 2001 +From: John Johansen +Date: Wed, 16 May 2012 10:58:05 -0700 +Subject: [PATCH 3/3] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount + +Add the ability for apparmor to do mediation of mount operations. Mount +rules require an updated apparmor_parser (2.8 series) for policy compilation. + +The basic form of the rules are. + + [audit] [deny] mount [conds]* [device] [ -> [conds] path], + [audit] [deny] remount [conds]* [path], + [audit] [deny] umount [conds]* [path], + [audit] [deny] pivotroot [oldroot=] + + remount is just a short cut for mount options=remount + + where [conds] can be + fstype= + options= + +Example mount commands + mount, # allow all mounts, but not umount or pivotroot + + mount fstype=procfs, # allow mounting procfs anywhere + + mount options=(bind, ro) /foo -> /bar, # readonly bind mount + + mount /dev/sda -> /mnt, + + mount /dev/sd** -> /mnt/**, + + mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ + + umount, + + umount /m*, + +See the apparmor userspace for full documentation + +Signed-off-by: John Johansen +Acked-by: Kees Cook +--- + security/apparmor/Makefile | 2 +- + security/apparmor/apparmorfs.c | 13 + + security/apparmor/domain.c | 2 +- + security/apparmor/include/apparmor.h | 3 +- + security/apparmor/include/audit.h | 11 + + security/apparmor/include/domain.h | 2 + + security/apparmor/include/mount.h | 54 +++ + security/apparmor/lsm.c | 60 ++++ + security/apparmor/mount.c | 616 +++++++++++++++++++++++++++++++++++ + 9 files changed, 760 insertions(+), 3 deletions(-) + create mode 100644 security/apparmor/include/mount.h + create mode 100644 security/apparmor/mount.c + +diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile +index a7dc10be232d..01368441f230 100644 +--- a/security/apparmor/Makefile ++++ b/security/apparmor/Makefile +@@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o + + apparmor-y := apparmorfs.o audit.o capability.o context.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 net.o ++ resource.o secid.o file.o policy_ns.o net.o mount.o + apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o + + clean-files := capability_names.h rlim_names.h net_names.h +diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c +index 4d236736cfb8..2e8d09e2368b 100644 +--- a/security/apparmor/apparmorfs.c ++++ b/security/apparmor/apparmorfs.c +@@ -1205,11 +1205,24 @@ static struct aa_fs_entry aa_fs_entry_policy[] = { + { } + }; + ++static struct aa_fs_entry aa_fs_entry_mount[] = { ++ AA_FS_FILE_STRING("mask", "mount umount"), ++ { } ++}; ++ ++static struct aa_fs_entry aa_fs_entry_namespaces[] = { ++ AA_FS_FILE_BOOLEAN("profile", 1), ++ AA_FS_FILE_BOOLEAN("pivot_root", 1), ++ { } ++}; ++ + static struct aa_fs_entry aa_fs_entry_features[] = { + AA_FS_DIR("policy", aa_fs_entry_policy), + AA_FS_DIR("domain", aa_fs_entry_domain), + AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), ++ AA_FS_DIR("mount", aa_fs_entry_mount), ++ AA_FS_DIR("namespaces", aa_fs_entry_namespaces), + AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), + AA_FS_DIR("rlimit", aa_fs_entry_rlimit), + AA_FS_DIR("caps", aa_fs_entry_caps), +diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c +index 001e133a3c8c..708b7e22b9b5 100644 +--- a/security/apparmor/domain.c ++++ b/security/apparmor/domain.c +@@ -237,7 +237,7 @@ static const char *next_name(int xtype, const char *name) + * + * Returns: refcounted profile, or NULL on failure (MAYBE NULL) + */ +-static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) ++struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) + { + struct aa_profile *new_profile = NULL; + struct aa_ns *ns = profile->ns; +diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h +index 1750cc0721c1..3383dc66f30f 100644 +--- a/security/apparmor/include/apparmor.h ++++ b/security/apparmor/include/apparmor.h +@@ -27,8 +27,9 @@ + #define AA_CLASS_NET 4 + #define AA_CLASS_RLIMITS 5 + #define AA_CLASS_DOMAIN 6 ++#define AA_CLASS_MOUNT 7 + +-#define AA_CLASS_LAST AA_CLASS_DOMAIN ++#define AA_CLASS_LAST AA_CLASS_MOUNT + + /* Control parameters settable through module/boot flags */ + extern enum audit_mode aa_g_audit; +diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h +index 0df708e8748b..41374ad89547 100644 +--- a/security/apparmor/include/audit.h ++++ b/security/apparmor/include/audit.h +@@ -70,6 +70,10 @@ enum audit_type { + #define OP_FMMAP "file_mmap" + #define OP_FMPROT "file_mprotect" + ++#define OP_PIVOTROOT "pivotroot" ++#define OP_MOUNT "mount" ++#define OP_UMOUNT "umount" ++ + #define OP_CREATE "create" + #define OP_POST_CREATE "post_create" + #define OP_BIND "bind" +@@ -127,6 +131,13 @@ struct apparmor_audit_data { + int rlim; + unsigned long max; + } rlim; ++ struct { ++ const char *src_name; ++ const char *type; ++ const char *trans; ++ const char *data; ++ unsigned long flags; ++ } mnt; + struct { + int type, protocol; + struct sock *sk; +diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h +index 30544729878a..7bd21d20a2bd 100644 +--- a/security/apparmor/include/domain.h ++++ b/security/apparmor/include/domain.h +@@ -23,6 +23,8 @@ struct aa_domain { + char **table; + }; + ++struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); ++ + int apparmor_bprm_set_creds(struct linux_binprm *bprm); + int apparmor_bprm_secureexec(struct linux_binprm *bprm); + void apparmor_bprm_committing_creds(struct linux_binprm *bprm); +diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h +new file mode 100644 +index 000000000000..a43b1d62e428 +--- /dev/null ++++ b/security/apparmor/include/mount.h +@@ -0,0 +1,54 @@ ++/* ++ * AppArmor security module ++ * ++ * This file contains AppArmor file mediation function definitions. ++ * ++ * Copyright 2012 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_MOUNT_H ++#define __AA_MOUNT_H ++ ++#include ++#include ++ ++#include "domain.h" ++#include "policy.h" ++ ++/* mount perms */ ++#define AA_MAY_PIVOTROOT 0x01 ++#define AA_MAY_MOUNT 0x02 ++#define AA_MAY_UMOUNT 0x04 ++#define AA_AUDIT_DATA 0x40 ++#define AA_CONT_MATCH 0x40 ++ ++#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) ++ ++int aa_remount(struct aa_profile *profile, const struct path *path, ++ unsigned long flags, void *data); ++ ++int aa_bind_mount(struct aa_profile *profile, const struct path *path, ++ const char *old_name, unsigned long flags); ++ ++ ++int aa_mount_change_type(struct aa_profile *profile, const struct path *path, ++ unsigned long flags); ++ ++int aa_move_mount(struct aa_profile *profile, const struct path *path, ++ const char *old_name); ++ ++int aa_new_mount(struct aa_profile *profile, const char *dev_name, ++ const struct path *path, const char *type, unsigned long flags, ++ void *data); ++ ++int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); ++ ++int aa_pivotroot(struct aa_profile *profile, const struct path *old_path, ++ const struct path *new_path); ++ ++#endif /* __AA_MOUNT_H */ +diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c +index e3017129a404..ee58a2cca74f 100644 +--- a/security/apparmor/lsm.c ++++ b/security/apparmor/lsm.c +@@ -38,6 +38,7 @@ + #include "include/policy.h" + #include "include/policy_ns.h" + #include "include/procattr.h" ++#include "include/mount.h" + + /* Flag indicating whether initialization completed */ + int apparmor_initialized __initdata; +@@ -479,6 +480,61 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); + } + ++static int apparmor_sb_mount(const char *dev_name, const struct path *path, ++ const char *type, unsigned long flags, void *data) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ /* Discard magic */ ++ if ((flags & MS_MGC_MSK) == MS_MGC_VAL) ++ flags &= ~MS_MGC_MSK; ++ ++ flags &= ~AA_MS_IGNORE_MASK; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) { ++ if (flags & MS_REMOUNT) ++ error = aa_remount(profile, path, flags, data); ++ else if (flags & MS_BIND) ++ error = aa_bind_mount(profile, path, dev_name, flags); ++ else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | ++ MS_UNBINDABLE)) ++ error = aa_mount_change_type(profile, path, flags); ++ else if (flags & MS_MOVE) ++ error = aa_move_mount(profile, path, dev_name); ++ else ++ error = aa_new_mount(profile, dev_name, path, type, ++ flags, data); ++ } ++ return error; ++} ++ ++static int apparmor_sb_umount(struct vfsmount *mnt, int flags) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) ++ error = aa_umount(profile, mnt, flags); ++ ++ return error; ++} ++ ++static int apparmor_sb_pivotroot(const struct path *old_path, ++ const struct path *new_path) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) ++ error = aa_pivotroot(profile, old_path, new_path); ++ ++ return error; ++} ++ + static int apparmor_getprocattr(struct task_struct *task, char *name, + char **value) + { +@@ -692,6 +748,10 @@ static struct security_hook_list apparmor_hooks[] = { + LSM_HOOK_INIT(capget, apparmor_capget), + LSM_HOOK_INIT(capable, apparmor_capable), + ++ LSM_HOOK_INIT(sb_mount, apparmor_sb_mount), ++ LSM_HOOK_INIT(sb_umount, apparmor_sb_umount), ++ LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot), ++ + LSM_HOOK_INIT(path_link, apparmor_path_link), + LSM_HOOK_INIT(path_unlink, apparmor_path_unlink), + LSM_HOOK_INIT(path_symlink, apparmor_path_symlink), +diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c +new file mode 100644 +index 000000000000..9e95a41c015c +--- /dev/null ++++ b/security/apparmor/mount.c +@@ -0,0 +1,616 @@ ++/* ++ * AppArmor security module ++ * ++ * This file contains AppArmor mediation of files ++ * ++ * Copyright (C) 1998-2008 Novell/SUSE ++ * Copyright 2009-2012 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 ++ ++#include "include/apparmor.h" ++#include "include/audit.h" ++#include "include/context.h" ++#include "include/domain.h" ++#include "include/file.h" ++#include "include/match.h" ++#include "include/mount.h" ++#include "include/path.h" ++#include "include/policy.h" ++ ++ ++static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) ++{ ++ if (flags & MS_RDONLY) ++ audit_log_format(ab, "ro"); ++ else ++ audit_log_format(ab, "rw"); ++ if (flags & MS_NOSUID) ++ audit_log_format(ab, ", nosuid"); ++ if (flags & MS_NODEV) ++ audit_log_format(ab, ", nodev"); ++ if (flags & MS_NOEXEC) ++ audit_log_format(ab, ", noexec"); ++ if (flags & MS_SYNCHRONOUS) ++ audit_log_format(ab, ", sync"); ++ if (flags & MS_REMOUNT) ++ audit_log_format(ab, ", remount"); ++ if (flags & MS_MANDLOCK) ++ audit_log_format(ab, ", mand"); ++ if (flags & MS_DIRSYNC) ++ audit_log_format(ab, ", dirsync"); ++ if (flags & MS_NOATIME) ++ audit_log_format(ab, ", noatime"); ++ if (flags & MS_NODIRATIME) ++ audit_log_format(ab, ", nodiratime"); ++ if (flags & MS_BIND) ++ audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); ++ if (flags & MS_MOVE) ++ audit_log_format(ab, ", move"); ++ if (flags & MS_SILENT) ++ audit_log_format(ab, ", silent"); ++ if (flags & MS_POSIXACL) ++ audit_log_format(ab, ", acl"); ++ if (flags & MS_UNBINDABLE) ++ audit_log_format(ab, flags & MS_REC ? ", runbindable" : ++ ", unbindable"); ++ if (flags & MS_PRIVATE) ++ audit_log_format(ab, flags & MS_REC ? ", rprivate" : ++ ", private"); ++ if (flags & MS_SLAVE) ++ audit_log_format(ab, flags & MS_REC ? ", rslave" : ++ ", slave"); ++ if (flags & MS_SHARED) ++ audit_log_format(ab, flags & MS_REC ? ", rshared" : ++ ", shared"); ++ if (flags & MS_RELATIME) ++ audit_log_format(ab, ", relatime"); ++ if (flags & MS_I_VERSION) ++ audit_log_format(ab, ", iversion"); ++ if (flags & MS_STRICTATIME) ++ audit_log_format(ab, ", strictatime"); ++ if (flags & MS_NOUSER) ++ audit_log_format(ab, ", nouser"); ++} ++ ++/** ++ * audit_cb - call back for mount specific audit fields ++ * @ab: audit_buffer (NOT NULL) ++ * @va: audit struct to audit values of (NOT NULL) ++ */ ++static void audit_cb(struct audit_buffer *ab, void *va) ++{ ++ struct common_audit_data *sa = va; ++ ++ if (aad(sa)->mnt.type) { ++ audit_log_format(ab, " fstype="); ++ audit_log_untrustedstring(ab, aad(sa)->mnt.type); ++ } ++ if (aad(sa)->mnt.src_name) { ++ audit_log_format(ab, " srcname="); ++ audit_log_untrustedstring(ab, aad(sa)->mnt.src_name); ++ } ++ if (aad(sa)->mnt.trans) { ++ audit_log_format(ab, " trans="); ++ audit_log_untrustedstring(ab, aad(sa)->mnt.trans); ++ } ++ if (aad(sa)->mnt.flags) { ++ audit_log_format(ab, " flags=\""); ++ audit_mnt_flags(ab, aad(sa)->mnt.flags); ++ audit_log_format(ab, "\""); ++ } ++ if (aad(sa)->mnt.data) { ++ audit_log_format(ab, " options="); ++ audit_log_untrustedstring(ab, aad(sa)->mnt.data); ++ } ++} ++ ++/** ++ * audit_mount - handle the auditing of mount operations ++ * @profile: the profile being enforced (NOT NULL) ++ * @gfp: allocation flags ++ * @op: operation being mediated (NOT NULL) ++ * @name: name of object being mediated (MAYBE NULL) ++ * @src_name: src_name of object being mediated (MAYBE_NULL) ++ * @type: type of filesystem (MAYBE_NULL) ++ * @trans: name of trans (MAYBE NULL) ++ * @flags: filesystem idependent mount flags ++ * @data: filesystem mount flags ++ * @request: permissions requested ++ * @perms: the permissions computed for the request (NOT NULL) ++ * @info: extra information message (MAYBE NULL) ++ * @error: 0 if operation allowed else failure error code ++ * ++ * Returns: %0 or error on failure ++ */ ++static int audit_mount(struct aa_profile *profile, gfp_t gfp, const char *op, ++ const char *name, const char *src_name, ++ const char *type, const char *trans, ++ unsigned long flags, const void *data, u32 request, ++ struct file_perms *perms, const char *info, int error) ++{ ++ int audit_type = AUDIT_APPARMOR_AUTO; ++ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op); ++ ++ if (likely(!error)) { ++ u32 mask = perms->audit; ++ ++ if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) ++ mask = 0xffff; ++ ++ /* mask off perms that are not being force audited */ ++ request &= mask; ++ ++ if (likely(!request)) ++ return 0; ++ audit_type = AUDIT_APPARMOR_AUDIT; ++ } else { ++ /* only report permissions that were denied */ ++ request = request & ~perms->allow; ++ ++ if (request & perms->kill) ++ audit_type = AUDIT_APPARMOR_KILL; ++ ++ /* quiet known rejects, assumes quiet and kill do not overlap */ ++ if ((request & perms->quiet) && ++ AUDIT_MODE(profile) != AUDIT_NOQUIET && ++ AUDIT_MODE(profile) != AUDIT_ALL) ++ request &= ~perms->quiet; ++ ++ if (!request) ++ return COMPLAIN_MODE(profile) ? ++ complain_error(error) : error; ++ } ++ ++ aad(&sa)->name = name; ++ aad(&sa)->mnt.src_name = src_name; ++ aad(&sa)->mnt.type = type; ++ aad(&sa)->mnt.trans = trans; ++ aad(&sa)->mnt.flags = flags; ++ if (data && (perms->audit & AA_AUDIT_DATA)) ++ aad(&sa)->mnt.data = data; ++ aad(&sa)->info = info; ++ aad(&sa)->error = error; ++ ++ return aa_audit(audit_type, profile, &sa, audit_cb); ++} ++ ++/** ++ * match_mnt_flags - Do an ordered match on mount flags ++ * @dfa: dfa to match against ++ * @state: state to start in ++ * @flags: mount flags to match against ++ * ++ * Mount flags are encoded as an ordered match. This is done instead of ++ * checking against a simple bitmask, to allow for logical operations ++ * on the flags. ++ * ++ * Returns: next state after flags match ++ */ ++static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, ++ unsigned long flags) ++{ ++ unsigned int i; ++ ++ for (i = 0; i <= 31 ; ++i) { ++ if ((1 << i) & flags) ++ state = aa_dfa_next(dfa, state, i + 1); ++ } ++ ++ return state; ++} ++ ++/** ++ * compute_mnt_perms - compute mount permission associated with @state ++ * @dfa: dfa to match against (NOT NULL) ++ * @state: state match finished in ++ * ++ * Returns: mount permissions ++ */ ++static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, ++ unsigned int state) ++{ ++ struct file_perms perms; ++ ++ perms.kill = 0; ++ perms.allow = dfa_user_allow(dfa, state); ++ perms.audit = dfa_user_audit(dfa, state); ++ perms.quiet = dfa_user_quiet(dfa, state); ++ perms.xindex = dfa_user_xindex(dfa, state); ++ ++ return perms; ++} ++ ++static const char *mnt_info_table[] = { ++ "match succeeded", ++ "failed mntpnt match", ++ "failed srcname match", ++ "failed type match", ++ "failed flags match", ++ "failed data match" ++}; ++ ++/* ++ * Returns 0 on success else element that match failed in, this is the ++ * index into the mnt_info_table above ++ */ ++static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, ++ const char *mntpnt, const char *devname, ++ const char *type, unsigned long flags, ++ void *data, bool binary, struct file_perms *perms) ++{ ++ unsigned int state; ++ ++ state = aa_dfa_match(dfa, start, mntpnt); ++ state = aa_dfa_null_transition(dfa, state); ++ if (!state) ++ return 1; ++ ++ if (devname) ++ state = aa_dfa_match(dfa, state, devname); ++ state = aa_dfa_null_transition(dfa, state); ++ if (!state) ++ return 2; ++ ++ if (type) ++ state = aa_dfa_match(dfa, state, type); ++ state = aa_dfa_null_transition(dfa, state); ++ if (!state) ++ return 3; ++ ++ state = match_mnt_flags(dfa, state, flags); ++ if (!state) ++ return 4; ++ *perms = compute_mnt_perms(dfa, state); ++ if (perms->allow & AA_MAY_MOUNT) ++ return 0; ++ ++ /* only match data if not binary and the DFA flags data is expected */ ++ if (data && !binary && (perms->allow & AA_CONT_MATCH)) { ++ state = aa_dfa_null_transition(dfa, state); ++ if (!state) ++ return 4; ++ ++ state = aa_dfa_match(dfa, state, data); ++ if (!state) ++ return 5; ++ *perms = compute_mnt_perms(dfa, state); ++ if (perms->allow & AA_MAY_MOUNT) ++ return 0; ++ } ++ ++ /* failed at end of flags match */ ++ return 4; ++} ++ ++/** ++ * match_mnt - handle path matching for mount ++ * @profile: the confining profile ++ * @mntpnt: string for the mntpnt (NOT NULL) ++ * @devname: string for the devname/src_name (MAYBE NULL) ++ * @type: string for the dev type (MAYBE NULL) ++ * @flags: mount flags to match ++ * @data: fs mount data (MAYBE NULL) ++ * @binary: whether @data is binary ++ * @perms: Returns: permission found by the match ++ * @info: Returns: infomation string about the match for logging ++ * ++ * Returns: 0 on success else error ++ */ ++static int match_mnt(struct aa_profile *profile, const char *mntpnt, ++ const char *devname, const char *type, ++ unsigned long flags, void *data, bool binary, ++ struct file_perms *perms, const char **info) ++{ ++ int pos; ++ ++ if (!profile->policy.dfa) ++ return -EACCES; ++ ++ pos = do_match_mnt(profile->policy.dfa, ++ profile->policy.start[AA_CLASS_MOUNT], ++ mntpnt, devname, type, flags, data, binary, perms); ++ if (pos) { ++ *info = mnt_info_table[pos]; ++ return -EACCES; ++ } ++ ++ return 0; ++} ++ ++static int path_flags(struct aa_profile *profile, const struct path *path) ++{ ++ return profile->path_flags | ++ S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; ++} ++ ++int aa_remount(struct aa_profile *profile, const struct path *path, ++ unsigned long flags, void *data) ++{ ++ struct file_perms perms = { }; ++ const char *name, *info = NULL; ++ char *buffer = NULL; ++ int binary, error; ++ ++ binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, NULL, NULL, flags, data, binary, ++ &perms, &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, ++ NULL, flags, data, AA_MAY_MOUNT, &perms, info, ++ error); ++ kfree(buffer); ++ ++ return error; ++} ++ ++int aa_bind_mount(struct aa_profile *profile, const struct path *path, ++ const char *dev_name, unsigned long flags) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL, *old_buffer = NULL; ++ const char *name, *old_name = NULL, *info = NULL; ++ struct path old_path; ++ int error; ++ ++ if (!dev_name || !*dev_name) ++ return -EINVAL; ++ ++ flags &= MS_REC | MS_BIND; ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); ++ if (error) ++ goto audit; ++ ++ error = aa_path_name(&old_path, path_flags(profile, &old_path), ++ &old_buffer, &old_name, &info); ++ path_put(&old_path); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, ++ &perms, &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, ++ NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, ++ info, error); ++ kfree(buffer); ++ kfree(old_buffer); ++ ++ return error; ++} ++ ++int aa_mount_change_type(struct aa_profile *profile, const struct path *path, ++ unsigned long flags) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL; ++ const char *name, *info = NULL; ++ int error; ++ ++ /* These are the flags allowed by do_change_type() */ ++ flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | ++ MS_UNBINDABLE); ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, ++ &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, ++ NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, ++ error); ++ kfree(buffer); ++ ++ return error; ++} ++ ++int aa_move_mount(struct aa_profile *profile, const struct path *path, ++ const char *orig_name) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL, *old_buffer = NULL; ++ const char *name, *old_name = NULL, *info = NULL; ++ struct path old_path; ++ int error; ++ ++ if (!orig_name || !*orig_name) ++ return -EINVAL; ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); ++ if (error) ++ goto audit; ++ ++ error = aa_path_name(&old_path, path_flags(profile, &old_path), ++ &old_buffer, &old_name, &info); ++ path_put(&old_path); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, ++ &perms, &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, ++ NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, ++ info, error); ++ kfree(buffer); ++ kfree(old_buffer); ++ ++ return error; ++} ++ ++int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, ++ const struct path *path, const char *type, unsigned long flags, ++ void *data) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL, *dev_buffer = NULL; ++ const char *name = NULL, *dev_name = NULL, *info = NULL; ++ int binary = 1; ++ int error; ++ ++ dev_name = orig_dev_name; ++ if (type) { ++ int requires_dev; ++ struct file_system_type *fstype = get_fs_type(type); ++ if (!fstype) ++ return -ENODEV; ++ ++ binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; ++ requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; ++ put_filesystem(fstype); ++ ++ if (requires_dev) { ++ struct path dev_path; ++ ++ if (!dev_name || !*dev_name) { ++ error = -ENOENT; ++ goto out; ++ } ++ ++ error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); ++ if (error) ++ goto audit; ++ ++ error = aa_path_name(&dev_path, ++ path_flags(profile, &dev_path), ++ &dev_buffer, &dev_name, &info); ++ path_put(&dev_path); ++ if (error) ++ goto audit; ++ } ++ } ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, dev_name, type, flags, data, binary, ++ &perms, &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, ++ type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, ++ error); ++ kfree(buffer); ++ kfree(dev_buffer); ++ ++out: ++ return error; ++ ++} ++ ++int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL; ++ const char *name, *info = NULL; ++ int error; ++ ++ struct path path = { mnt, mnt->mnt_root }; ++ error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ if (!error && profile->policy.dfa) { ++ unsigned int state; ++ state = aa_dfa_match(profile->policy.dfa, ++ profile->policy.start[AA_CLASS_MOUNT], ++ name); ++ perms = compute_mnt_perms(profile->policy.dfa, state); ++ } ++ ++ if (AA_MAY_UMOUNT & ~perms.allow) ++ error = -EACCES; ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, ++ NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); ++ kfree(buffer); ++ ++ return error; ++} ++ ++int aa_pivotroot(struct aa_profile *profile, const struct path *old_path, ++ const struct path *new_path) ++{ ++ struct file_perms perms = { }; ++ struct aa_profile *target = NULL; ++ char *old_buffer = NULL, *new_buffer = NULL; ++ const char *old_name, *new_name = NULL, *info = NULL; ++ int error; ++ ++ error = aa_path_name(old_path, path_flags(profile, old_path), ++ &old_buffer, &old_name, &info); ++ if (error) ++ goto audit; ++ ++ error = aa_path_name(new_path, path_flags(profile, new_path), ++ &new_buffer, &new_name, &info); ++ if (error) ++ goto audit; ++ ++ if (profile->policy.dfa) { ++ unsigned int state; ++ state = aa_dfa_match(profile->policy.dfa, ++ profile->policy.start[AA_CLASS_MOUNT], ++ new_name); ++ state = aa_dfa_null_transition(profile->policy.dfa, state); ++ state = aa_dfa_match(profile->policy.dfa, state, old_name); ++ perms = compute_mnt_perms(profile->policy.dfa, state); ++ } ++ ++ if (AA_MAY_PIVOTROOT & perms.allow) { ++ if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { ++ target = x_table_lookup(profile, perms.xindex); ++ if (!target) ++ error = -ENOENT; ++ else ++ error = aa_replace_current_profile(target); ++ } ++ } else ++ error = -EACCES; ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, ++ old_name, NULL, target ? target->base.name : NULL, ++ 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); ++ aa_put_profile(target); ++ kfree(old_buffer); ++ kfree(new_buffer); ++ ++ return error; ++} +-- +2.11.0 + diff --git a/kernel-patches/v4.8/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch b/kernel-patches/v4.8/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch new file mode 100644 index 000000000..0b05c9ec9 --- /dev/null +++ b/kernel-patches/v4.8/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch @@ -0,0 +1,603 @@ +From 269384ead6a3c82ac31fd3778e899ccc6a54358e Mon Sep 17 00:00:00 2001 +From: John Johansen +Date: Mon, 4 Oct 2010 15:03:36 -0700 +Subject: [PATCH 1/3] UBUNTU: SAUCE: AppArmor: basic networking rules + +Base support for network mediation. + +Signed-off-by: John Johansen +--- + security/apparmor/.gitignore | 1 + + security/apparmor/Makefile | 42 +++++++++- + security/apparmor/apparmorfs.c | 1 + + security/apparmor/include/audit.h | 4 + + security/apparmor/include/net.h | 44 ++++++++++ + security/apparmor/include/policy.h | 3 + + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ + security/apparmor/net.c | 162 +++++++++++++++++++++++++++++++++++++ + security/apparmor/policy.c | 1 + + security/apparmor/policy_unpack.c | 46 +++++++++++ + 10 files changed, 414 insertions(+), 2 deletions(-) + create mode 100644 security/apparmor/include/net.h + create mode 100644 security/apparmor/net.c + +diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore +index 9cdec70d72b8..d5b291e94264 100644 +--- a/security/apparmor/.gitignore ++++ b/security/apparmor/.gitignore +@@ -1,5 +1,6 @@ + # + # Generated include files + # ++net_names.h + capability_names.h + rlim_names.h +diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile +index d693df874818..5dbb72f46452 100644 +--- a/security/apparmor/Makefile ++++ b/security/apparmor/Makefile +@@ -4,10 +4,10 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o + + apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ + path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ +- resource.o sid.o file.o ++ resource.o sid.o file.o net.o + apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o + +-clean-files := capability_names.h rlim_names.h ++clean-files := capability_names.h rlim_names.h net_names.h + + + # Build a lower case string table of capability names +@@ -25,6 +25,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ + -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \ + tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + ++# Build a lower case string table of address family names ++# Transform lines from ++# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ ++# #define AF_INET 2 /* Internet IP Protocol */ ++# to ++# [1] = "local", ++# [2] = "inet", ++# ++# and build the securityfs entries for the mapping. ++# Transforms lines from ++# #define AF_INET 2 /* Internet IP Protocol */ ++# to ++# #define AA_FS_AF_MASK "local inet" ++quiet_cmd_make-af = GEN $@ ++cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ ++ sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ ++ 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ ++ echo "};" >> $@ ;\ ++ echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ ++ sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ ++ $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ ++ ++# Build a lower case string table of sock type names ++# Transform lines from ++# SOCK_STREAM = 1, ++# to ++# [1] = "stream", ++quiet_cmd_make-sock = GEN $@ ++cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ ++ sed $^ >>$@ -r -n \ ++ -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ ++ echo "};" >> $@ + + # Build a lower case string table of rlimit names. + # Transforms lines from +@@ -61,6 +93,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ + tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + + $(obj)/capability.o : $(obj)/capability_names.h ++$(obj)/net.o : $(obj)/net_names.h + $(obj)/resource.o : $(obj)/rlim_names.h + $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ + $(src)/Makefile +@@ -68,3 +101,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ + $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ + $(src)/Makefile + $(call cmd,make-rlim) ++$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ ++ $(srctree)/include/linux/net.h \ ++ $(src)/Makefile ++ $(call cmd,make-af) ++ $(call cmd,make-sock) +diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c +index 729e595119ed..181d961e6d58 100644 +--- a/security/apparmor/apparmorfs.c ++++ b/security/apparmor/apparmorfs.c +@@ -807,6 +807,7 @@ static struct aa_fs_entry aa_fs_entry_features[] = { + AA_FS_DIR("policy", aa_fs_entry_policy), + AA_FS_DIR("domain", aa_fs_entry_domain), + AA_FS_DIR("file", aa_fs_entry_file), ++ AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), + AA_FS_DIR("rlimit", aa_fs_entry_rlimit), + AA_FS_DIR("caps", aa_fs_entry_caps), +diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h +index ba3dfd17f23f..5d3c419b17d9 100644 +--- a/security/apparmor/include/audit.h ++++ b/security/apparmor/include/audit.h +@@ -125,6 +125,10 @@ struct apparmor_audit_data { + u32 denied; + kuid_t ouid; + } fs; ++ struct { ++ int type, protocol; ++ struct sock *sk; ++ } net; + }; + }; + +diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h +new file mode 100644 +index 000000000000..cb8a12109b7a +--- /dev/null ++++ b/security/apparmor/include/net.h +@@ -0,0 +1,44 @@ ++/* ++ * AppArmor security module ++ * ++ * This file contains AppArmor network mediation definitions. ++ * ++ * Copyright (C) 1998-2008 Novell/SUSE ++ * Copyright 2009-2012 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_NET_H ++#define __AA_NET_H ++ ++#include ++ ++#include "apparmorfs.h" ++ ++/* struct aa_net - network confinement data ++ * @allowed: basic network families permissions ++ * @audit_network: which network permissions to force audit ++ * @quiet_network: which network permissions to quiet rejects ++ */ ++struct aa_net { ++ u16 allow[AF_MAX]; ++ u16 audit[AF_MAX]; ++ u16 quiet[AF_MAX]; ++}; ++ ++extern struct aa_fs_entry aa_fs_entry_network[]; ++ ++extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, ++ int type, int protocol, struct sock *sk); ++extern int aa_revalidate_sk(int op, struct sock *sk); ++ ++static inline void aa_free_net_rules(struct aa_net *new) ++{ ++ /* NOP */ ++} ++ ++#endif /* __AA_NET_H */ +diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h +index 52275f040a5f..4fc4dacc1101 100644 +--- a/security/apparmor/include/policy.h ++++ b/security/apparmor/include/policy.h +@@ -27,6 +27,7 @@ + #include "capability.h" + #include "domain.h" + #include "file.h" ++#include "net.h" + #include "resource.h" + + extern const char *const aa_profile_mode_names[]; +@@ -176,6 +177,7 @@ struct aa_replacedby { + * @policy: general match rules governing policy + * @file: The set of rules governing basic file access and domain transitions + * @caps: capabilities for the profile ++ * @net: network controls for the profile + * @rlimits: rlimits for the profile + * + * @dents: dentries for the profiles file entries in apparmorfs +@@ -217,6 +219,7 @@ struct aa_profile { + struct aa_policydb policy; + struct aa_file_rules file; + struct aa_caps caps; ++ struct aa_net net; + struct aa_rlimit rlimits; + + unsigned char *hash; +diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c +index 41b8cb115801..d96b5f7c1912 100644 +--- a/security/apparmor/lsm.c ++++ b/security/apparmor/lsm.c +@@ -32,6 +32,7 @@ + #include "include/context.h" + #include "include/file.h" + #include "include/ipc.h" ++#include "include/net.h" + #include "include/path.h" + #include "include/policy.h" + #include "include/procattr.h" +@@ -584,6 +585,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, + return error; + } + ++static int apparmor_socket_create(int family, int type, int protocol, int kern) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ if (kern) ++ return 0; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) ++ error = aa_net_perm(OP_CREATE, profile, family, type, protocol, ++ NULL); ++ return error; ++} ++ ++static int apparmor_socket_bind(struct socket *sock, ++ struct sockaddr *address, int addrlen) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_BIND, sk); ++} ++ ++static int apparmor_socket_connect(struct socket *sock, ++ struct sockaddr *address, int addrlen) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_CONNECT, sk); ++} ++ ++static int apparmor_socket_listen(struct socket *sock, int backlog) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_LISTEN, sk); ++} ++ ++static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_ACCEPT, sk); ++} ++ ++static int apparmor_socket_sendmsg(struct socket *sock, ++ struct msghdr *msg, int size) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_SENDMSG, sk); ++} ++ ++static int apparmor_socket_recvmsg(struct socket *sock, ++ struct msghdr *msg, int size, int flags) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_RECVMSG, sk); ++} ++ ++static int apparmor_socket_getsockname(struct socket *sock) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_GETSOCKNAME, sk); ++} ++ ++static int apparmor_socket_getpeername(struct socket *sock) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_GETPEERNAME, sk); ++} ++ ++static int apparmor_socket_getsockopt(struct socket *sock, int level, ++ int optname) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_GETSOCKOPT, sk); ++} ++ ++static int apparmor_socket_setsockopt(struct socket *sock, int level, ++ int optname) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_SETSOCKOPT, sk); ++} ++ ++static int apparmor_socket_shutdown(struct socket *sock, int how) ++{ ++ struct sock *sk = sock->sk; ++ ++ return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); ++} ++ + static struct security_hook_list apparmor_hooks[] = { + LSM_HOOK_INIT(ptrace_access_check, apparmor_ptrace_access_check), + LSM_HOOK_INIT(ptrace_traceme, apparmor_ptrace_traceme), +@@ -613,6 +712,19 @@ static struct security_hook_list apparmor_hooks[] = { + LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), + LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), + ++ LSM_HOOK_INIT(socket_create, apparmor_socket_create), ++ LSM_HOOK_INIT(socket_bind, apparmor_socket_bind), ++ LSM_HOOK_INIT(socket_connect, apparmor_socket_connect), ++ LSM_HOOK_INIT(socket_listen, apparmor_socket_listen), ++ LSM_HOOK_INIT(socket_accept, apparmor_socket_accept), ++ LSM_HOOK_INIT(socket_sendmsg, apparmor_socket_sendmsg), ++ LSM_HOOK_INIT(socket_recvmsg, apparmor_socket_recvmsg), ++ LSM_HOOK_INIT(socket_getsockname, apparmor_socket_getsockname), ++ LSM_HOOK_INIT(socket_getpeername, apparmor_socket_getpeername), ++ LSM_HOOK_INIT(socket_getsockopt, apparmor_socket_getsockopt), ++ LSM_HOOK_INIT(socket_setsockopt, apparmor_socket_setsockopt), ++ LSM_HOOK_INIT(socket_shutdown, apparmor_socket_shutdown), ++ + LSM_HOOK_INIT(cred_alloc_blank, apparmor_cred_alloc_blank), + LSM_HOOK_INIT(cred_free, apparmor_cred_free), + LSM_HOOK_INIT(cred_prepare, apparmor_cred_prepare), +diff --git a/security/apparmor/net.c b/security/apparmor/net.c +new file mode 100644 +index 000000000000..003dd18c61a5 +--- /dev/null ++++ b/security/apparmor/net.c +@@ -0,0 +1,162 @@ ++/* ++ * AppArmor security module ++ * ++ * This file contains AppArmor network mediation ++ * ++ * Copyright (C) 1998-2008 Novell/SUSE ++ * Copyright 2009-2012 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/apparmor.h" ++#include "include/audit.h" ++#include "include/context.h" ++#include "include/net.h" ++#include "include/policy.h" ++ ++#include "net_names.h" ++ ++struct aa_fs_entry aa_fs_entry_network[] = { ++ AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), ++ { } ++}; ++ ++/* audit callback for net specific fields */ ++static void audit_cb(struct audit_buffer *ab, void *va) ++{ ++ struct common_audit_data *sa = va; ++ ++ audit_log_format(ab, " family="); ++ if (address_family_names[sa->u.net->family]) { ++ audit_log_string(ab, address_family_names[sa->u.net->family]); ++ } else { ++ audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); ++ } ++ audit_log_format(ab, " sock_type="); ++ if (sock_type_names[sa->aad->net.type]) { ++ audit_log_string(ab, sock_type_names[sa->aad->net.type]); ++ } else { ++ audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); ++ } ++ audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); ++} ++ ++/** ++ * audit_net - audit network access ++ * @profile: profile being enforced (NOT NULL) ++ * @op: operation being checked ++ * @family: network family ++ * @type: network type ++ * @protocol: network protocol ++ * @sk: socket auditing is being applied to ++ * @error: error code for failure else 0 ++ * ++ * Returns: %0 or sa->error else other errorcode on failure ++ */ ++static int audit_net(struct aa_profile *profile, int op, u16 family, int type, ++ int protocol, struct sock *sk, int error) ++{ ++ int audit_type = AUDIT_APPARMOR_AUTO; ++ struct common_audit_data sa; ++ struct apparmor_audit_data aad = { }; ++ struct lsm_network_audit net = { }; ++ if (sk) { ++ sa.type = LSM_AUDIT_DATA_NET; ++ } else { ++ sa.type = LSM_AUDIT_DATA_NONE; ++ } ++ /* todo fill in socket addr info */ ++ sa.aad = &aad; ++ sa.u.net = &net; ++ sa.aad->op = op, ++ sa.u.net->family = family; ++ sa.u.net->sk = sk; ++ sa.aad->net.type = type; ++ sa.aad->net.protocol = protocol; ++ sa.aad->error = error; ++ ++ if (likely(!sa.aad->error)) { ++ u16 audit_mask = profile->net.audit[sa.u.net->family]; ++ if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && ++ !(1 << sa.aad->net.type & audit_mask))) ++ return 0; ++ audit_type = AUDIT_APPARMOR_AUDIT; ++ } else { ++ u16 quiet_mask = profile->net.quiet[sa.u.net->family]; ++ u16 kill_mask = 0; ++ u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; ++ ++ if (denied & kill_mask) ++ audit_type = AUDIT_APPARMOR_KILL; ++ ++ if ((denied & quiet_mask) && ++ AUDIT_MODE(profile) != AUDIT_NOQUIET && ++ AUDIT_MODE(profile) != AUDIT_ALL) ++ return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; ++ } ++ ++ return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); ++} ++ ++/** ++ * aa_net_perm - very course network access check ++ * @op: operation being checked ++ * @profile: profile being enforced (NOT NULL) ++ * @family: network family ++ * @type: network type ++ * @protocol: network protocol ++ * ++ * Returns: %0 else error if permission denied ++ */ ++int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, ++ int protocol, struct sock *sk) ++{ ++ u16 family_mask; ++ int error; ++ ++ if ((family < 0) || (family >= AF_MAX)) ++ return -EINVAL; ++ ++ if ((type < 0) || (type >= SOCK_MAX)) ++ return -EINVAL; ++ ++ /* unix domain and netlink sockets are handled by ipc */ ++ if (family == AF_UNIX || family == AF_NETLINK) ++ return 0; ++ ++ family_mask = profile->net.allow[family]; ++ ++ error = (family_mask & (1 << type)) ? 0 : -EACCES; ++ ++ return audit_net(profile, op, family, type, protocol, sk, error); ++} ++ ++/** ++ * aa_revalidate_sk - Revalidate access to a sock ++ * @op: operation being checked ++ * @sk: sock being revalidated (NOT NULL) ++ * ++ * Returns: %0 else error if permission denied ++ */ ++int aa_revalidate_sk(int op, struct sock *sk) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ /* aa_revalidate_sk should not be called from interrupt context ++ * don't mediate these calls as they are not task related ++ */ ++ if (in_interrupt()) ++ return 0; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) ++ error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, ++ sk->sk_protocol, sk); ++ ++ return error; ++} +diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c +index 179e68d7dc5f..f1a8541760e8 100644 +--- a/security/apparmor/policy.c ++++ b/security/apparmor/policy.c +@@ -603,6 +603,7 @@ void aa_free_profile(struct aa_profile *profile) + + aa_free_file_rules(&profile->file); + aa_free_cap_rules(&profile->caps); ++ aa_free_net_rules(&profile->net); + aa_free_rlimit_rules(&profile->rlimits); + + kzfree(profile->dirname); +diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c +index 138120698f83..7dc15ff91299 100644 +--- a/security/apparmor/policy_unpack.c ++++ b/security/apparmor/policy_unpack.c +@@ -193,6 +193,19 @@ fail: + 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((u16 *) 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)) { +@@ -476,6 +489,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) + { + struct aa_profile *profile = NULL; + const char *name = NULL; ++ size_t size = 0; + int i, error = -EPROTO; + kernel_cap_t tmpcap; + u32 tmp; +@@ -576,6 +590,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) + if (!unpack_rlimits(e, profile)) + goto fail; + ++ size = unpack_array(e, "net_allowed_af"); ++ if (size) { ++ ++ 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.allow[i], NULL)) ++ goto fail; ++ if (!unpack_u16(e, &profile->net.audit[i], NULL)) ++ goto fail; ++ if (!unpack_u16(e, &profile->net.quiet[i], NULL)) ++ goto fail; ++ } ++ if (!unpack_nameX(e, AA_ARRAYEND, NULL)) ++ goto fail; ++ } ++ /* ++ * allow unix domain and netlink sockets they are handled ++ * by IPC ++ */ ++ profile->net.allow[AF_UNIX] = 0xffff; ++ profile->net.allow[AF_NETLINK] = 0xffff; ++ + if (unpack_nameX(e, AA_STRUCT, "policydb")) { + /* generic policy dfa - optional and may be NULL */ + profile->policy.dfa = unpack_dfa(e); +-- +2.11.0 + diff --git a/kernel-patches/v4.8/0002-apparmor-Fix-quieting-of-audit-messages-for-network-.patch b/kernel-patches/v4.8/0002-apparmor-Fix-quieting-of-audit-messages-for-network-.patch new file mode 100644 index 000000000..13599327e --- /dev/null +++ b/kernel-patches/v4.8/0002-apparmor-Fix-quieting-of-audit-messages-for-network-.patch @@ -0,0 +1,38 @@ +From 88ba6f37ed824ca24901fca7e399168db5e46f12 Mon Sep 17 00:00:00 2001 +From: John Johansen +Date: Fri, 29 Jun 2012 17:34:00 -0700 +Subject: [PATCH 2/3] apparmor: Fix quieting of audit messages for network + mediation + +If a profile specified a quieting of network denials for a given rule by +either the quiet or deny rule qualifiers, the resultant quiet mask for +denied requests was applied incorrectly, resulting in two potential bugs. +1. The misapplied quiet mask would prevent denials from being correctly + tested against the kill mask/mode. Thus network access requests that + should have resulted in the application being killed did not. + +2. The actual quieting of the denied network request was not being applied. + This would result in network rejections always being logged even when + they had been specifically marked as quieted. + +Signed-off-by: John Johansen +--- + security/apparmor/net.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/security/apparmor/net.c b/security/apparmor/net.c +index 003dd18c61a5..6e6e5c981006 100644 +--- a/security/apparmor/net.c ++++ b/security/apparmor/net.c +@@ -88,7 +88,7 @@ static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; +- u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; ++ u16 denied = (1 << sa.aad->net.type); + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; +-- +2.11.0 + diff --git a/kernel-patches/v4.8/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch b/kernel-patches/v4.8/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch new file mode 100644 index 000000000..c298f1339 --- /dev/null +++ b/kernel-patches/v4.8/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch @@ -0,0 +1,962 @@ +From 6556d6523f74e90a801503d28e7b8dcc5caa6a1b Mon Sep 17 00:00:00 2001 +From: John Johansen +Date: Wed, 16 May 2012 10:58:05 -0700 +Subject: [PATCH 3/3] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount + +Add the ability for apparmor to do mediation of mount operations. Mount +rules require an updated apparmor_parser (2.8 series) for policy compilation. + +The basic form of the rules are. + + [audit] [deny] mount [conds]* [device] [ -> [conds] path], + [audit] [deny] remount [conds]* [path], + [audit] [deny] umount [conds]* [path], + [audit] [deny] pivotroot [oldroot=] + + remount is just a short cut for mount options=remount + + where [conds] can be + fstype= + options= + +Example mount commands + mount, # allow all mounts, but not umount or pivotroot + + mount fstype=procfs, # allow mounting procfs anywhere + + mount options=(bind, ro) /foo -> /bar, # readonly bind mount + + mount /dev/sda -> /mnt, + + mount /dev/sd** -> /mnt/**, + + mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ + + umount, + + umount /m*, + +See the apparmor userspace for full documentation + +Signed-off-by: John Johansen +Acked-by: Kees Cook +--- + security/apparmor/Makefile | 2 +- + security/apparmor/apparmorfs.c | 15 +- + security/apparmor/audit.c | 4 + + security/apparmor/domain.c | 2 +- + security/apparmor/include/apparmor.h | 3 +- + security/apparmor/include/audit.h | 11 + + security/apparmor/include/domain.h | 2 + + security/apparmor/include/mount.h | 54 +++ + security/apparmor/lsm.c | 60 ++++ + security/apparmor/mount.c | 620 +++++++++++++++++++++++++++++++++++ + 10 files changed, 769 insertions(+), 4 deletions(-) + create mode 100644 security/apparmor/include/mount.h + create mode 100644 security/apparmor/mount.c + +diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile +index 5dbb72f46452..89b344541868 100644 +--- a/security/apparmor/Makefile ++++ b/security/apparmor/Makefile +@@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o + + apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ + path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ +- resource.o sid.o file.o net.o ++ resource.o sid.o file.o net.o mount.o + apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o + + clean-files := capability_names.h rlim_names.h net_names.h +diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c +index 181d961e6d58..5fb67f60bace 100644 +--- a/security/apparmor/apparmorfs.c ++++ b/security/apparmor/apparmorfs.c +@@ -800,7 +800,18 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { + + static struct aa_fs_entry aa_fs_entry_policy[] = { + AA_FS_FILE_BOOLEAN("set_load", 1), +- {} ++ { } ++}; ++ ++static struct aa_fs_entry aa_fs_entry_mount[] = { ++ AA_FS_FILE_STRING("mask", "mount umount"), ++ { } ++}; ++ ++static struct aa_fs_entry aa_fs_entry_namespaces[] = { ++ AA_FS_FILE_BOOLEAN("profile", 1), ++ AA_FS_FILE_BOOLEAN("pivot_root", 1), ++ { } + }; + + static struct aa_fs_entry aa_fs_entry_features[] = { +@@ -808,6 +819,8 @@ static struct aa_fs_entry aa_fs_entry_features[] = { + AA_FS_DIR("domain", aa_fs_entry_domain), + AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), ++ AA_FS_DIR("mount", aa_fs_entry_mount), ++ AA_FS_DIR("namespaces", aa_fs_entry_namespaces), + AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), + AA_FS_DIR("rlimit", aa_fs_entry_rlimit), + AA_FS_DIR("caps", aa_fs_entry_caps), +diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c +index 3a7f1da1425e..c2a8b8ac38a7 100644 +--- a/security/apparmor/audit.c ++++ b/security/apparmor/audit.c +@@ -44,6 +44,10 @@ const char *const op_table[] = { + "file_mmap", + "file_mprotect", + ++ "pivotroot", ++ "mount", ++ "umount", ++ + "create", + "post_create", + "bind", +diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c +index fc3036b34e51..f2a83b4430db 100644 +--- a/security/apparmor/domain.c ++++ b/security/apparmor/domain.c +@@ -236,7 +236,7 @@ static const char *next_name(int xtype, const char *name) + * + * Returns: refcounted profile, or NULL on failure (MAYBE NULL) + */ +-static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) ++struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) + { + struct aa_profile *new_profile = NULL; + struct aa_namespace *ns = profile->ns; +diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h +index 5d721e990876..b57da7b9f8bd 100644 +--- a/security/apparmor/include/apparmor.h ++++ b/security/apparmor/include/apparmor.h +@@ -30,8 +30,9 @@ + #define AA_CLASS_NET 4 + #define AA_CLASS_RLIMITS 5 + #define AA_CLASS_DOMAIN 6 ++#define AA_CLASS_MOUNT 7 + +-#define AA_CLASS_LAST AA_CLASS_DOMAIN ++#define AA_CLASS_LAST AA_CLASS_MOUNT + + /* Control parameters settable through module/boot flags */ + extern enum audit_mode aa_g_audit; +diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h +index 5d3c419b17d9..b9f1d57984ca 100644 +--- a/security/apparmor/include/audit.h ++++ b/security/apparmor/include/audit.h +@@ -72,6 +72,10 @@ enum aa_ops { + OP_FMMAP, + OP_FMPROT, + ++ OP_PIVOTROOT, ++ OP_MOUNT, ++ OP_UMOUNT, ++ + OP_CREATE, + OP_POST_CREATE, + OP_BIND, +@@ -120,6 +124,13 @@ struct apparmor_audit_data { + unsigned long max; + } rlim; + struct { ++ const char *src_name; ++ const char *type; ++ const char *trans; ++ const char *data; ++ unsigned long flags; ++ } mnt; ++ struct { + const char *target; + u32 request; + u32 denied; +diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h +index de04464f0a3f..a3f70c58ef3d 100644 +--- a/security/apparmor/include/domain.h ++++ b/security/apparmor/include/domain.h +@@ -23,6 +23,8 @@ struct aa_domain { + char **table; + }; + ++struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); ++ + int apparmor_bprm_set_creds(struct linux_binprm *bprm); + int apparmor_bprm_secureexec(struct linux_binprm *bprm); + void apparmor_bprm_committing_creds(struct linux_binprm *bprm); +diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h +new file mode 100644 +index 000000000000..a43b1d62e428 +--- /dev/null ++++ b/security/apparmor/include/mount.h +@@ -0,0 +1,54 @@ ++/* ++ * AppArmor security module ++ * ++ * This file contains AppArmor file mediation function definitions. ++ * ++ * Copyright 2012 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_MOUNT_H ++#define __AA_MOUNT_H ++ ++#include ++#include ++ ++#include "domain.h" ++#include "policy.h" ++ ++/* mount perms */ ++#define AA_MAY_PIVOTROOT 0x01 ++#define AA_MAY_MOUNT 0x02 ++#define AA_MAY_UMOUNT 0x04 ++#define AA_AUDIT_DATA 0x40 ++#define AA_CONT_MATCH 0x40 ++ ++#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) ++ ++int aa_remount(struct aa_profile *profile, const struct path *path, ++ unsigned long flags, void *data); ++ ++int aa_bind_mount(struct aa_profile *profile, const struct path *path, ++ const char *old_name, unsigned long flags); ++ ++ ++int aa_mount_change_type(struct aa_profile *profile, const struct path *path, ++ unsigned long flags); ++ ++int aa_move_mount(struct aa_profile *profile, const struct path *path, ++ const char *old_name); ++ ++int aa_new_mount(struct aa_profile *profile, const char *dev_name, ++ const struct path *path, const char *type, unsigned long flags, ++ void *data); ++ ++int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); ++ ++int aa_pivotroot(struct aa_profile *profile, const struct path *old_path, ++ const struct path *new_path); ++ ++#endif /* __AA_MOUNT_H */ +diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c +index d96b5f7c1912..5ff9984cba5a 100644 +--- a/security/apparmor/lsm.c ++++ b/security/apparmor/lsm.c +@@ -36,6 +36,7 @@ + #include "include/path.h" + #include "include/policy.h" + #include "include/procattr.h" ++#include "include/mount.h" + + /* Flag indicating whether initialization completed */ + int apparmor_initialized __initdata; +@@ -469,6 +470,61 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); + } + ++static int apparmor_sb_mount(const char *dev_name, const struct path *path, ++ const char *type, unsigned long flags, void *data) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ /* Discard magic */ ++ if ((flags & MS_MGC_MSK) == MS_MGC_VAL) ++ flags &= ~MS_MGC_MSK; ++ ++ flags &= ~AA_MS_IGNORE_MASK; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) { ++ if (flags & MS_REMOUNT) ++ error = aa_remount(profile, path, flags, data); ++ else if (flags & MS_BIND) ++ error = aa_bind_mount(profile, path, dev_name, flags); ++ else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | ++ MS_UNBINDABLE)) ++ error = aa_mount_change_type(profile, path, flags); ++ else if (flags & MS_MOVE) ++ error = aa_move_mount(profile, path, dev_name); ++ else ++ error = aa_new_mount(profile, dev_name, path, type, ++ flags, data); ++ } ++ return error; ++} ++ ++static int apparmor_sb_umount(struct vfsmount *mnt, int flags) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) ++ error = aa_umount(profile, mnt, flags); ++ ++ return error; ++} ++ ++static int apparmor_sb_pivotroot(const struct path *old_path, ++ const struct path *new_path) ++{ ++ struct aa_profile *profile; ++ int error = 0; ++ ++ profile = __aa_current_profile(); ++ if (!unconfined(profile)) ++ error = aa_pivotroot(profile, old_path, new_path); ++ ++ return error; ++} ++ + static int apparmor_getprocattr(struct task_struct *task, char *name, + char **value) + { +@@ -689,6 +745,10 @@ static struct security_hook_list apparmor_hooks[] = { + LSM_HOOK_INIT(capget, apparmor_capget), + LSM_HOOK_INIT(capable, apparmor_capable), + ++ LSM_HOOK_INIT(sb_mount, apparmor_sb_mount), ++ LSM_HOOK_INIT(sb_umount, apparmor_sb_umount), ++ LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot), ++ + LSM_HOOK_INIT(path_link, apparmor_path_link), + LSM_HOOK_INIT(path_unlink, apparmor_path_unlink), + LSM_HOOK_INIT(path_symlink, apparmor_path_symlink), +diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c +new file mode 100644 +index 000000000000..9cf9170b4976 +--- /dev/null ++++ b/security/apparmor/mount.c +@@ -0,0 +1,620 @@ ++/* ++ * AppArmor security module ++ * ++ * This file contains AppArmor mediation of files ++ * ++ * Copyright (C) 1998-2008 Novell/SUSE ++ * Copyright 2009-2012 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 ++ ++#include "include/apparmor.h" ++#include "include/audit.h" ++#include "include/context.h" ++#include "include/domain.h" ++#include "include/file.h" ++#include "include/match.h" ++#include "include/mount.h" ++#include "include/path.h" ++#include "include/policy.h" ++ ++ ++static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) ++{ ++ if (flags & MS_RDONLY) ++ audit_log_format(ab, "ro"); ++ else ++ audit_log_format(ab, "rw"); ++ if (flags & MS_NOSUID) ++ audit_log_format(ab, ", nosuid"); ++ if (flags & MS_NODEV) ++ audit_log_format(ab, ", nodev"); ++ if (flags & MS_NOEXEC) ++ audit_log_format(ab, ", noexec"); ++ if (flags & MS_SYNCHRONOUS) ++ audit_log_format(ab, ", sync"); ++ if (flags & MS_REMOUNT) ++ audit_log_format(ab, ", remount"); ++ if (flags & MS_MANDLOCK) ++ audit_log_format(ab, ", mand"); ++ if (flags & MS_DIRSYNC) ++ audit_log_format(ab, ", dirsync"); ++ if (flags & MS_NOATIME) ++ audit_log_format(ab, ", noatime"); ++ if (flags & MS_NODIRATIME) ++ audit_log_format(ab, ", nodiratime"); ++ if (flags & MS_BIND) ++ audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); ++ if (flags & MS_MOVE) ++ audit_log_format(ab, ", move"); ++ if (flags & MS_SILENT) ++ audit_log_format(ab, ", silent"); ++ if (flags & MS_POSIXACL) ++ audit_log_format(ab, ", acl"); ++ if (flags & MS_UNBINDABLE) ++ audit_log_format(ab, flags & MS_REC ? ", runbindable" : ++ ", unbindable"); ++ if (flags & MS_PRIVATE) ++ audit_log_format(ab, flags & MS_REC ? ", rprivate" : ++ ", private"); ++ if (flags & MS_SLAVE) ++ audit_log_format(ab, flags & MS_REC ? ", rslave" : ++ ", slave"); ++ if (flags & MS_SHARED) ++ audit_log_format(ab, flags & MS_REC ? ", rshared" : ++ ", shared"); ++ if (flags & MS_RELATIME) ++ audit_log_format(ab, ", relatime"); ++ if (flags & MS_I_VERSION) ++ audit_log_format(ab, ", iversion"); ++ if (flags & MS_STRICTATIME) ++ audit_log_format(ab, ", strictatime"); ++ if (flags & MS_NOUSER) ++ audit_log_format(ab, ", nouser"); ++} ++ ++/** ++ * audit_cb - call back for mount specific audit fields ++ * @ab: audit_buffer (NOT NULL) ++ * @va: audit struct to audit values of (NOT NULL) ++ */ ++static void audit_cb(struct audit_buffer *ab, void *va) ++{ ++ struct common_audit_data *sa = va; ++ ++ if (sa->aad->mnt.type) { ++ audit_log_format(ab, " fstype="); ++ audit_log_untrustedstring(ab, sa->aad->mnt.type); ++ } ++ if (sa->aad->mnt.src_name) { ++ audit_log_format(ab, " srcname="); ++ audit_log_untrustedstring(ab, sa->aad->mnt.src_name); ++ } ++ if (sa->aad->mnt.trans) { ++ audit_log_format(ab, " trans="); ++ audit_log_untrustedstring(ab, sa->aad->mnt.trans); ++ } ++ if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { ++ audit_log_format(ab, " flags=\""); ++ audit_mnt_flags(ab, sa->aad->mnt.flags); ++ audit_log_format(ab, "\""); ++ } ++ if (sa->aad->mnt.data) { ++ audit_log_format(ab, " options="); ++ audit_log_untrustedstring(ab, sa->aad->mnt.data); ++ } ++} ++ ++/** ++ * audit_mount - handle the auditing of mount operations ++ * @profile: the profile being enforced (NOT NULL) ++ * @gfp: allocation flags ++ * @op: operation being mediated (NOT NULL) ++ * @name: name of object being mediated (MAYBE NULL) ++ * @src_name: src_name of object being mediated (MAYBE_NULL) ++ * @type: type of filesystem (MAYBE_NULL) ++ * @trans: name of trans (MAYBE NULL) ++ * @flags: filesystem idependent mount flags ++ * @data: filesystem mount flags ++ * @request: permissions requested ++ * @perms: the permissions computed for the request (NOT NULL) ++ * @info: extra information message (MAYBE NULL) ++ * @error: 0 if operation allowed else failure error code ++ * ++ * Returns: %0 or error on failure ++ */ ++static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, ++ const char *name, const char *src_name, ++ const char *type, const char *trans, ++ unsigned long flags, const void *data, u32 request, ++ struct file_perms *perms, const char *info, int error) ++{ ++ int audit_type = AUDIT_APPARMOR_AUTO; ++ struct common_audit_data sa = { }; ++ struct apparmor_audit_data aad = { }; ++ ++ if (likely(!error)) { ++ u32 mask = perms->audit; ++ ++ if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) ++ mask = 0xffff; ++ ++ /* mask off perms that are not being force audited */ ++ request &= mask; ++ ++ if (likely(!request)) ++ return 0; ++ audit_type = AUDIT_APPARMOR_AUDIT; ++ } else { ++ /* only report permissions that were denied */ ++ request = request & ~perms->allow; ++ ++ if (request & perms->kill) ++ audit_type = AUDIT_APPARMOR_KILL; ++ ++ /* quiet known rejects, assumes quiet and kill do not overlap */ ++ if ((request & perms->quiet) && ++ AUDIT_MODE(profile) != AUDIT_NOQUIET && ++ AUDIT_MODE(profile) != AUDIT_ALL) ++ request &= ~perms->quiet; ++ ++ if (!request) ++ return COMPLAIN_MODE(profile) ? ++ complain_error(error) : error; ++ } ++ ++ sa.type = LSM_AUDIT_DATA_NONE; ++ sa.aad = &aad; ++ sa.aad->op = op; ++ sa.aad->name = name; ++ sa.aad->mnt.src_name = src_name; ++ sa.aad->mnt.type = type; ++ sa.aad->mnt.trans = trans; ++ sa.aad->mnt.flags = flags; ++ if (data && (perms->audit & AA_AUDIT_DATA)) ++ sa.aad->mnt.data = data; ++ sa.aad->info = info; ++ sa.aad->error = error; ++ ++ return aa_audit(audit_type, profile, gfp, &sa, audit_cb); ++} ++ ++/** ++ * match_mnt_flags - Do an ordered match on mount flags ++ * @dfa: dfa to match against ++ * @state: state to start in ++ * @flags: mount flags to match against ++ * ++ * Mount flags are encoded as an ordered match. This is done instead of ++ * checking against a simple bitmask, to allow for logical operations ++ * on the flags. ++ * ++ * Returns: next state after flags match ++ */ ++static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, ++ unsigned long flags) ++{ ++ unsigned int i; ++ ++ for (i = 0; i <= 31 ; ++i) { ++ if ((1 << i) & flags) ++ state = aa_dfa_next(dfa, state, i + 1); ++ } ++ ++ return state; ++} ++ ++/** ++ * compute_mnt_perms - compute mount permission associated with @state ++ * @dfa: dfa to match against (NOT NULL) ++ * @state: state match finished in ++ * ++ * Returns: mount permissions ++ */ ++static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, ++ unsigned int state) ++{ ++ struct file_perms perms; ++ ++ perms.kill = 0; ++ perms.allow = dfa_user_allow(dfa, state); ++ perms.audit = dfa_user_audit(dfa, state); ++ perms.quiet = dfa_user_quiet(dfa, state); ++ perms.xindex = dfa_user_xindex(dfa, state); ++ ++ return perms; ++} ++ ++static const char const *mnt_info_table[] = { ++ "match succeeded", ++ "failed mntpnt match", ++ "failed srcname match", ++ "failed type match", ++ "failed flags match", ++ "failed data match" ++}; ++ ++/* ++ * Returns 0 on success else element that match failed in, this is the ++ * index into the mnt_info_table above ++ */ ++static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, ++ const char *mntpnt, const char *devname, ++ const char *type, unsigned long flags, ++ void *data, bool binary, struct file_perms *perms) ++{ ++ unsigned int state; ++ ++ state = aa_dfa_match(dfa, start, mntpnt); ++ state = aa_dfa_null_transition(dfa, state); ++ if (!state) ++ return 1; ++ ++ if (devname) ++ state = aa_dfa_match(dfa, state, devname); ++ state = aa_dfa_null_transition(dfa, state); ++ if (!state) ++ return 2; ++ ++ if (type) ++ state = aa_dfa_match(dfa, state, type); ++ state = aa_dfa_null_transition(dfa, state); ++ if (!state) ++ return 3; ++ ++ state = match_mnt_flags(dfa, state, flags); ++ if (!state) ++ return 4; ++ *perms = compute_mnt_perms(dfa, state); ++ if (perms->allow & AA_MAY_MOUNT) ++ return 0; ++ ++ /* only match data if not binary and the DFA flags data is expected */ ++ if (data && !binary && (perms->allow & AA_CONT_MATCH)) { ++ state = aa_dfa_null_transition(dfa, state); ++ if (!state) ++ return 4; ++ ++ state = aa_dfa_match(dfa, state, data); ++ if (!state) ++ return 5; ++ *perms = compute_mnt_perms(dfa, state); ++ if (perms->allow & AA_MAY_MOUNT) ++ return 0; ++ } ++ ++ /* failed at end of flags match */ ++ return 4; ++} ++ ++/** ++ * match_mnt - handle path matching for mount ++ * @profile: the confining profile ++ * @mntpnt: string for the mntpnt (NOT NULL) ++ * @devname: string for the devname/src_name (MAYBE NULL) ++ * @type: string for the dev type (MAYBE NULL) ++ * @flags: mount flags to match ++ * @data: fs mount data (MAYBE NULL) ++ * @binary: whether @data is binary ++ * @perms: Returns: permission found by the match ++ * @info: Returns: infomation string about the match for logging ++ * ++ * Returns: 0 on success else error ++ */ ++static int match_mnt(struct aa_profile *profile, const char *mntpnt, ++ const char *devname, const char *type, ++ unsigned long flags, void *data, bool binary, ++ struct file_perms *perms, const char **info) ++{ ++ int pos; ++ ++ if (!profile->policy.dfa) ++ return -EACCES; ++ ++ pos = do_match_mnt(profile->policy.dfa, ++ profile->policy.start[AA_CLASS_MOUNT], ++ mntpnt, devname, type, flags, data, binary, perms); ++ if (pos) { ++ *info = mnt_info_table[pos]; ++ return -EACCES; ++ } ++ ++ return 0; ++} ++ ++static int path_flags(struct aa_profile *profile, const struct path *path) ++{ ++ return profile->path_flags | ++ S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; ++} ++ ++int aa_remount(struct aa_profile *profile, const struct path *path, ++ unsigned long flags, void *data) ++{ ++ struct file_perms perms = { }; ++ const char *name, *info = NULL; ++ char *buffer = NULL; ++ int binary, error; ++ ++ binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, NULL, NULL, flags, data, binary, ++ &perms, &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, ++ NULL, flags, data, AA_MAY_MOUNT, &perms, info, ++ error); ++ kfree(buffer); ++ ++ return error; ++} ++ ++int aa_bind_mount(struct aa_profile *profile, const struct path *path, ++ const char *dev_name, unsigned long flags) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL, *old_buffer = NULL; ++ const char *name, *old_name = NULL, *info = NULL; ++ struct path old_path; ++ int error; ++ ++ if (!dev_name || !*dev_name) ++ return -EINVAL; ++ ++ flags &= MS_REC | MS_BIND; ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); ++ if (error) ++ goto audit; ++ ++ error = aa_path_name(&old_path, path_flags(profile, &old_path), ++ &old_buffer, &old_name, &info); ++ path_put(&old_path); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, ++ &perms, &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, ++ NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, ++ info, error); ++ kfree(buffer); ++ kfree(old_buffer); ++ ++ return error; ++} ++ ++int aa_mount_change_type(struct aa_profile *profile, const struct path *path, ++ unsigned long flags) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL; ++ const char *name, *info = NULL; ++ int error; ++ ++ /* These are the flags allowed by do_change_type() */ ++ flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | ++ MS_UNBINDABLE); ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, ++ &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, ++ NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, ++ error); ++ kfree(buffer); ++ ++ return error; ++} ++ ++int aa_move_mount(struct aa_profile *profile, const struct path *path, ++ const char *orig_name) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL, *old_buffer = NULL; ++ const char *name, *old_name = NULL, *info = NULL; ++ struct path old_path; ++ int error; ++ ++ if (!orig_name || !*orig_name) ++ return -EINVAL; ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); ++ if (error) ++ goto audit; ++ ++ error = aa_path_name(&old_path, path_flags(profile, &old_path), ++ &old_buffer, &old_name, &info); ++ path_put(&old_path); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, ++ &perms, &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, ++ NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, ++ info, error); ++ kfree(buffer); ++ kfree(old_buffer); ++ ++ return error; ++} ++ ++int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, ++ const struct path *path, const char *type, unsigned long flags, ++ void *data) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL, *dev_buffer = NULL; ++ const char *name = NULL, *dev_name = NULL, *info = NULL; ++ int binary = 1; ++ int error; ++ ++ dev_name = orig_dev_name; ++ if (type) { ++ int requires_dev; ++ struct file_system_type *fstype = get_fs_type(type); ++ if (!fstype) ++ return -ENODEV; ++ ++ binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; ++ requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; ++ put_filesystem(fstype); ++ ++ if (requires_dev) { ++ struct path dev_path; ++ ++ if (!dev_name || !*dev_name) { ++ error = -ENOENT; ++ goto out; ++ } ++ ++ error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); ++ if (error) ++ goto audit; ++ ++ error = aa_path_name(&dev_path, ++ path_flags(profile, &dev_path), ++ &dev_buffer, &dev_name, &info); ++ path_put(&dev_path); ++ if (error) ++ goto audit; ++ } ++ } ++ ++ error = aa_path_name(path, path_flags(profile, path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ error = match_mnt(profile, name, dev_name, type, flags, data, binary, ++ &perms, &info); ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, ++ type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, ++ error); ++ kfree(buffer); ++ kfree(dev_buffer); ++ ++out: ++ return error; ++ ++} ++ ++int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) ++{ ++ struct file_perms perms = { }; ++ char *buffer = NULL; ++ const char *name, *info = NULL; ++ int error; ++ ++ struct path path = { mnt, mnt->mnt_root }; ++ error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, ++ &info); ++ if (error) ++ goto audit; ++ ++ if (!error && profile->policy.dfa) { ++ unsigned int state; ++ state = aa_dfa_match(profile->policy.dfa, ++ profile->policy.start[AA_CLASS_MOUNT], ++ name); ++ perms = compute_mnt_perms(profile->policy.dfa, state); ++ } ++ ++ if (AA_MAY_UMOUNT & ~perms.allow) ++ error = -EACCES; ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, ++ NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); ++ kfree(buffer); ++ ++ return error; ++} ++ ++int aa_pivotroot(struct aa_profile *profile, const struct path *old_path, ++ const struct path *new_path) ++{ ++ struct file_perms perms = { }; ++ struct aa_profile *target = NULL; ++ char *old_buffer = NULL, *new_buffer = NULL; ++ const char *old_name, *new_name = NULL, *info = NULL; ++ int error; ++ ++ error = aa_path_name(old_path, path_flags(profile, old_path), ++ &old_buffer, &old_name, &info); ++ if (error) ++ goto audit; ++ ++ error = aa_path_name(new_path, path_flags(profile, new_path), ++ &new_buffer, &new_name, &info); ++ if (error) ++ goto audit; ++ ++ if (profile->policy.dfa) { ++ unsigned int state; ++ state = aa_dfa_match(profile->policy.dfa, ++ profile->policy.start[AA_CLASS_MOUNT], ++ new_name); ++ state = aa_dfa_null_transition(profile->policy.dfa, state); ++ state = aa_dfa_match(profile->policy.dfa, state, old_name); ++ perms = compute_mnt_perms(profile->policy.dfa, state); ++ } ++ ++ if (AA_MAY_PIVOTROOT & perms.allow) { ++ if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { ++ target = x_table_lookup(profile, perms.xindex); ++ if (!target) ++ error = -ENOENT; ++ else ++ error = aa_replace_current_profile(target); ++ } ++ } else ++ error = -EACCES; ++ ++audit: ++ error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, ++ old_name, NULL, target ? target->base.name : NULL, ++ 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); ++ aa_put_profile(target); ++ kfree(old_buffer); ++ kfree(new_buffer); ++ ++ return error; ++} +-- +2.11.0 +