summaryrefslogtreecommitdiff
path: root/security/apparmor/net.c
diff options
context:
space:
mode:
authorJohn Johansen <john.johansen@canonical.com>2022-09-07 12:46:30 -0700
committerJohn Johansen <john.johansen@canonical.com>2025-01-18 06:47:12 -0800
commitc05e705812d179f4b85aeacc34a555a42bc4f9ac (patch)
tree82878c6d4d617e5c4b77e3ef81b612dcc11b4235 /security/apparmor/net.c
parentb4940d913cc2c67f8f6bf17abbf3e5301f95e260 (diff)
apparmor: add fine grained af_unix mediation
Extend af_unix mediation to support fine grained controls based on the type (abstract, anonymous, fs), the address, and the labeling on the socket. This allows for using socket addresses to label and the socket and control which subjects can communicate. The unix rule format follows standard apparmor rules except that fs based unix sockets can be mediated by existing file rules. None fs unix sockets can be mediated by a unix socket rule. Where The address of an abstract unix domain socket begins with the @ character, similar to how they are reported (as paths) by netstat -x. The address then follows and may contain pattern matching and any characters including the null character. In apparmor null characters must be specified by using an escape sequence \000 or \x00. The pattern matching is the same as is used by file path matching so * will not match / even though it has no special meaning with in an abstract socket name. Eg. allow unix addr=@*, Autobound unix domain sockets have a unix sun_path assigned to them by the kernel, as such specifying a policy based address is not possible. The autobinding of sockets can be controlled by specifying the special auto keyword. Eg. allow unix addr=auto, To indicate that the rule only applies to auto binding of unix domain sockets. It is important to note this only applies to the bind permission as once the socket is bound to an address it is indistinguishable from a socket that have an addr bound with a specified name. When the auto keyword is used with other permissions or as part of a peer addr it will be replaced with a pattern that can match an autobound socket. Eg. For some kernels allow unix rw addr=auto, It is important to note, this pattern may match abstract sockets that were not autobound but have an addr that fits what is generated by the kernel when autobinding a socket. Anonymous unix domain sockets have no sun_path associated with the socket address, however it can be specified with the special none keyword to indicate the rule only applies to anonymous unix domain sockets. Eg. allow unix addr=none, If the address component of a rule is not specified then the rule applies to autobind, abstract and anonymous sockets. The label on the socket can be compared using the standard label= rule conditional. Eg. allow unix addr=@foo peer=(label=bar), see man apparmor.d for full syntax description. Signed-off-by: John Johansen <john.johansen@canonical.com>
Diffstat (limited to 'security/apparmor/net.c')
-rw-r--r--security/apparmor/net.c142
1 files changed, 112 insertions, 30 deletions
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
index c76e0f5dcc93..a256a4664826 100644
--- a/security/apparmor/net.c
+++ b/security/apparmor/net.c
@@ -8,6 +8,7 @@
* Copyright 2009-2017 Canonical Ltd.
*/
+#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/cred.h"
@@ -24,6 +25,12 @@ struct aa_sfs_entry aa_sfs_entry_network[] = {
{ }
};
+struct aa_sfs_entry aa_sfs_entry_networkv9[] = {
+ AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK),
+ AA_SFS_FILE_BOOLEAN("af_unix", 1),
+ { }
+};
+
static const char * const net_mask_names[] = {
"unknown",
"send",
@@ -66,6 +73,37 @@ static const char * const net_mask_names[] = {
"unknown",
};
+static void audit_unix_addr(struct audit_buffer *ab, const char *str,
+ struct sockaddr_un *addr, int addrlen)
+{
+ int len = unix_addr_len(addrlen);
+
+ if (!addr || len <= 0) {
+ audit_log_format(ab, " %s=none", str);
+ } else if (addr->sun_path[0]) {
+ audit_log_format(ab, " %s=", str);
+ audit_log_untrustedstring(ab, addr->sun_path);
+ } else {
+ audit_log_format(ab, " %s=\"@", str);
+ if (audit_string_contains_control(&addr->sun_path[1], len - 1))
+ audit_log_n_hex(ab, &addr->sun_path[1], len - 1);
+ else
+ audit_log_format(ab, "%.*s", len - 1,
+ &addr->sun_path[1]);
+ audit_log_format(ab, "\"");
+ }
+}
+
+static void audit_unix_sk_addr(struct audit_buffer *ab, const char *str,
+ const struct sock *sk)
+{
+ const struct unix_sock *u = unix_sk(sk);
+
+ if (u && u->addr)
+ audit_unix_addr(ab, str, u->addr->name, u->addr->len);
+ else
+ audit_unix_addr(ab, str, NULL, 0);
+}
/* audit callback for net specific fields */
void audit_net_cb(struct audit_buffer *ab, void *va)
@@ -73,12 +111,12 @@ void audit_net_cb(struct audit_buffer *ab, void *va)
struct common_audit_data *sa = va;
struct apparmor_audit_data *ad = aad(sa);
- if (address_family_names[sa->u.net->family])
+ if (address_family_names[ad->common.u.net->family])
audit_log_format(ab, " family=\"%s\"",
- address_family_names[sa->u.net->family]);
+ address_family_names[ad->common.u.net->family]);
else
audit_log_format(ab, " family=\"unknown(%d)\"",
- sa->u.net->family);
+ ad->common.u.net->family);
if (sock_type_names[ad->net.type])
audit_log_format(ab, " sock_type=\"%s\"",
sock_type_names[ad->net.type]);
@@ -98,6 +136,23 @@ void audit_net_cb(struct audit_buffer *ab, void *va)
net_mask_names, NET_PERMS_MASK);
}
}
+ if (ad->common.u.net->family == PF_UNIX) {
+ if ((ad->request & ~NET_PEER_MASK) && ad->net.addr)
+ audit_unix_addr(ab, "addr",
+ unix_addr(ad->net.addr),
+ ad->net.addrlen);
+ else
+ audit_unix_sk_addr(ab, "addr", ad->common.u.net->sk);
+ if (ad->request & NET_PEER_MASK) {
+ if (ad->net.addr)
+ audit_unix_addr(ab, "peer_addr",
+ unix_addr(ad->net.addr),
+ ad->net.addrlen);
+ else
+ audit_unix_sk_addr(ab, "peer_addr",
+ ad->net.peer_sk);
+ }
+ }
if (ad->peer) {
audit_log_format(ab, " peer=");
aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
@@ -106,9 +161,9 @@ void audit_net_cb(struct audit_buffer *ab, void *va)
}
/* standard permission lookup pattern - supports early bailout */
-static int do_perms(struct aa_profile *profile, struct aa_policydb *policy,
- unsigned int state, u32 request,
- struct aa_perms *p, struct apparmor_audit_data *ad)
+int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy,
+ aa_state_t state, u32 request,
+ struct aa_perms *p, struct apparmor_audit_data *ad)
{
struct aa_perms perms;
@@ -140,31 +195,53 @@ static struct aa_perms *early_match(struct aa_policydb *policy,
return p;
}
-/* passing in state returned by PROFILE_MEDIATES_AF */
+static aa_state_t aa_dfa_match_be16(struct aa_dfa *dfa, aa_state_t state,
+ u16 data)
+{
+ __be16 buffer = cpu_to_be16(data);
+
+ return aa_dfa_match_len(dfa, state, (char *) &buffer, 2);
+}
+
+/**
+ * aa_match_to_prot - match the af, type, protocol triplet
+ * @policy: policy being matched
+ * @state: state to start in
+ * @request: permissions being requested, ignored if @p == NULL
+ * @af: socket address family
+ * @type: socket type
+ * @protocol: socket protocol
+ * @p: output - pointer to permission associated with match
+ * @info: output - pointer to string describing failure
+ *
+ * RETURNS: state match stopped in.
+ *
+ * If @(p) is assigned a value the returned state will be the
+ * corresponding state. Will not set @p on failure or if match completes
+ * only if an early match occurs
+ */
aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state,
- u32 request, u16 family, int type, int protocol,
+ u32 request, u16 af, int type, int protocol,
struct aa_perms **p, const char **info)
{
- __be16 buffer;
-
- buffer = cpu_to_be16(family);
- state = aa_dfa_match_len(policy->dfa, state, (char *) &buffer, 2);
+ state = aa_dfa_match_be16(policy->dfa, state, (u16)af);
if (!state) {
*info = "failed af match";
- return DFA_NOMATCH;
+ return state;
}
- buffer = cpu_to_be16((u16)type);
- state = aa_dfa_match_len(policy->dfa, state, (char *) &buffer, 2);
- if (!state)
+ state = aa_dfa_match_be16(policy->dfa, state, (u16)type);
+ if (state) {
+ if (p)
+ *p = early_match(policy, state, request);
+ if (!p || !*p) {
+ state = aa_dfa_match_be16(policy->dfa, state, (u16)protocol);
+ if (!state)
+ *info = "failed protocol match";
+ }
+ } else {
*info = "failed type match";
- *p = early_match(policy, state, request);
- if (!*p) {
- buffer = cpu_to_be16((u16)protocol);
- state = aa_dfa_match_len(policy->dfa, state, (char *) &buffer,
- 2);
- if (!state)
- *info = "failed protocol match";
}
+
return state;
}
@@ -182,20 +259,21 @@ int aa_profile_af_perm(struct aa_profile *profile,
AA_BUG(type < 0 || type >= SOCK_MAX);
AA_BUG(profile_unconfined(profile));
- state = RULE_MEDIATES(rules, AA_CLASS_NET);
+ if (profile_unconfined(profile))
+ return 0;
+ state = RULE_MEDIATES_NET(rules);
if (!state)
return 0;
-
state = aa_match_to_prot(rules->policy, state, request, family, type,
protocol, &p, &ad->info);
- return do_perms(profile, rules->policy, state, request, p, ad);
+ return aa_do_perms(profile, rules->policy, state, request, p, ad);
}
int aa_af_perm(const struct cred *subj_cred, struct aa_label *label,
const char *op, u32 request, u16 family, int type, int protocol)
{
struct aa_profile *profile;
- DEFINE_AUDIT_NET(ad, op, NULL, family, type, protocol);
+ DEFINE_AUDIT_NET(ad, op, subj_cred, NULL, family, type, protocol);
return fn_for_each_confined(label, profile,
aa_profile_af_perm(profile, &ad, request, family,
@@ -215,7 +293,7 @@ static int aa_label_sk_perm(const struct cred *subj_cred,
if (ctx->label != kernel_t && !unconfined(label)) {
struct aa_profile *profile;
- DEFINE_AUDIT_SK(ad, op, sk);
+ DEFINE_AUDIT_SK(ad, op, subj_cred, sk);
ad.subj_cred = subj_cred;
error = fn_for_each_confined(label, profile,
@@ -243,12 +321,16 @@ int aa_sk_perm(const char *op, u32 request, struct sock *sk)
int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label,
- const char *op, u32 request, struct socket *sock)
+ const char *op, u32 request, struct file *file)
{
+ struct socket *sock = (struct socket *) file->private_data;
+
AA_BUG(!label);
AA_BUG(!sock);
AA_BUG(!sock->sk);
+ if (sock->sk->sk_family == PF_UNIX)
+ return aa_unix_file_perm(subj_cred, label, op, request, file);
return aa_label_sk_perm(subj_cred, label, op, request, sock->sk);
}
@@ -313,7 +395,7 @@ int apparmor_secmark_check(struct aa_label *label, char *op, u32 request,
u32 secid, const struct sock *sk)
{
struct aa_profile *profile;
- DEFINE_AUDIT_SK(ad, op, sk);
+ DEFINE_AUDIT_SK(ad, op, NULL, sk);
return fn_for_each_confined(label, profile,
aa_secmark_perm(profile, request, secid,