[apparmor] [patch 06/12] parser: Add support for unix domain socket rules.

Steve Beattie steve at nxnw.org
Wed Aug 20 07:58:41 UTC 2014


On Fri, Aug 15, 2014 at 12:20:41PM -0700, john.johansen at canonical.com wrote:
> 
> 

I'm slowly working my way through this. A couple of code comments follow:

> --- /dev/null
> +++ 2.9-test/parser/af_rule.cc
> @@ -0,0 +1,171 @@
> +/*
> + *   Copyright (c) 2014
> + *   Canonical, Ltd. (All rights reserved)
> + *
> + *   This program is free software; you can redistribute it and/or
> + *   modify it under the terms of version 2 of the GNU General Public
> + *   License published by the Free Software Foundation.
> + *
> + *   This program is distributed in the hope that it will be useful,
> + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *   GNU General Public License for more details.
> + *
> + *   You should have received a copy of the GNU General Public License
> + *   along with this program; if not, contact Novell, Inc. or Canonical
> + *   Ltd.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/apparmor.h>
> +
> +#include <iomanip>
> +#include <string>
> +#include <iostream>
> +#include <sstream>
> +
> +#include "network.h"
> +#include "parser.h"
> +#include "profile.h"
> +#include "parser_yacc.h"
> +#include "af_rule.h"
> +
> +
> +/* need to move this table to stl */
> +static struct supported_cond supported_conds[] = {
> +	{ "type", true, false, false, local_cond },
> +	{ "protocol", false, false, false, local_cond },
> +	{ "label", true, false, false, peer_cond },
> +	{ NULL, false, false, false, local_cond },	/* eol sentinal */
> +};
> +
> +int af_rule::cond_check(struct supported_cond *conds, struct cond_entry *ent,
> +			bool peer, const char *rname)
> +{
> +	struct supported_cond *i;
> +	for (i = conds; i->name; i++) {
> +		if (strcmp(ent->name, i->name) != 0)
> +			continue;
> +		if (!i->supported)
> +			yyerror("%s rule: '%s' conditional is not currently supported\n", rname, ent->name);
> +		if (!peer && (i->side == peer_cond))
> +			yyerror("%s rule: '%s' conditional is only valid in the peer expression\n", rname, ent->name);
> +		if (peer && (i->side == local_cond))
> +			yyerror("%s rule: '%s' conditional is not allowed in the peer expression\n", rname, ent->name);
> +		if (!ent->eq && !i->in)
> +			yyerror("%s rule: keyword 'in' is not allowed in '%s' socket conditional\n", rname, ent->name);
> +		if (list_len(ent->vals) > 1 && !i->multivalue)
> +			yyerror("%s rule: conditional '%s' only supports a single value\n", rname, ent->name);
> +		return true;
> +	}
> +
> +	/* not in support table */
> +	return false;
> +}
> +
> +/* generic af supported conds.
> + * returns: true if processed, else false
> + */
> +int af_rule::move_base_cond(struct cond_entry *ent, bool peer)
> +{
> +	if (!cond_check(supported_conds, ent, peer, "unknown"))
> +		return false;
> +
> +	if (strcmp(ent->name, "type") == 0) {
> +		move_conditional_value("socket rule", &sock_type, ent);
> +		sock_type_n = net_find_type_val(sock_type);
> +		if (sock_type_n == -1)
> +			yyerror("socket rule: invalid socket type '%s'", sock_type);
> +	} else if (strcmp(ent->name, "protocol") == 0) {
> +		yyerror("socket rule: 'protocol' conditional is not currently supported\n");
> +	} else if (strcmp(ent->name, "label") == 0) {
> +		if (peer)
> +			move_conditional_value("unix", &label, ent);
> +		else
> +			move_conditional_value("unix", &peer_label, ent);

Is the logic above here correct? Or am I really confused about what
'peer' is supposed to represent?

> +	} else
> +		return false;
> +
> +	return true;
> +}
> +
> +ostream &af_rule::dump_prefix(ostream &os)
> +{
> +	if (audit)
> +		os << "audit ";
> +	if (deny)
> +		os << "deny ";
> +	return os;
> +}
[SNIP]

> --- /dev/null
> +++ 2.9-test/parser/af_unix.cc
> @@ -0,0 +1,386 @@
> +/*
> + *   Copyright (c) 2014
> + *   Canonical, Ltd. (All rights reserved)
> + *
> + *   This program is free software; you can redistribute it and/or
> + *   modify it under the terms of version 2 of the GNU General Public
> + *   License published by the Free Software Foundation.
> + *
> + *   This program is distributed in the hope that it will be useful,
> + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *   GNU General Public License for more details.
> + *
> + *   You should have received a copy of the GNU General Public License
> + *   along with this program; if not, contact Novell, Inc. or Canonical
> + *   Ltd.
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/apparmor.h>
> +
> +#include <iomanip>
> +#include <string>
> +#include <iostream>
> +#include <sstream>
> +
> +#include "network.h"
> +#include "parser.h"
> +#include "profile.h"
> +#include "af_unix.h"
> +
> +int parse_unix_mode(const char *str_mode, int *mode, int fail)
> +{
> +	return parse_X_mode("unix", AA_VALID_NET_PERMS, str_mode, mode, fail);
> +}
> +
> +
> +static struct supported_cond supported_conds[] = {
> +	{ "path", true, false, false, either_cond },
> +	{ NULL, false, false, false, local_cond },	/* sentinal */
> +};
> +
> +void unix_rule::move_conditionals(struct cond_entry *conds)
> +{
> +	struct cond_entry *ent;
> +
> +	list_for_each(conds, ent) {
> +
> +		if (!cond_check(supported_conds, ent, false, "unix") &&
> +		    !move_base_cond(ent, false)) {
> +			yyerror("unix rule: invalid conditional '%s'\n",
> +				ent->name);
> +			continue;
> +		}
> +		if (strcmp(ent->name, "path") == 0) {
> +			move_conditional_value("unix socket", &path, ent);
> +			if (path[0] != '@' && strcmp(path, "none") != 0)
> +				yyerror("unix rule: invalid value for path='%s'\n", path);
> +		}
> +
> +		/* TODO: add conditionals for
> +		 *   listen queue length
> +		 *   attrs that can be read/set
> +		 *   ops that can be read/set
> +		 * allow in on
> +		 *   type, protocol
> +		 * local label match, and set
> +		 */
> +	}
> +}
> +
> +void unix_rule::move_peer_conditionals(struct cond_entry *conds)
> +{
> +	struct cond_entry *ent;
> +
> +	list_for_each(conds, ent) {
> +		if (!cond_check(supported_conds, ent, true, "unix") &&
> +		    !move_base_cond(ent, true)) {
> +			yyerror("unix rule: invalid peer conditional '%s'\n",
> +				ent->name);
> +			continue;
> +		}
> +		if (strcmp(ent->name, "path") == 0) {
> +			move_conditional_value("unix", &peer_path, ent);
> +			if (peer_path[0] != '@' && strcmp(path, "none") != 0)
> +				yyerror("unix rule: invalid value for path='%s'\n", peer_path);
> +		}
> +	}
> +}
> +
> +unix_rule::unix_rule(unsigned int type_p, bool audit_p, bool denied):
> +	af_rule("unix"), path(NULL), peer_path(NULL)
> +{
> +	if (type_p != 0xffffffff) {
> +		sock_type_n = type_p;
> +		sock_type = strdup(net_find_type_name(type_p));
> +		if (!sock_type)
> +			yyerror("socket rule: invalid socket type '%d'", type_p);
> +	}
> +	mode = AA_VALID_NET_PERMS;
> +	audit = audit_p ? AA_VALID_NET_PERMS : 0;
> +	deny = denied;
> +}
> +
> +unix_rule::unix_rule(int mode_p, struct cond_entry *conds,
> +		     struct cond_entry *peer_conds):
> +	af_rule("unix"), path(NULL), peer_path(NULL)
> +{
> +	move_conditionals(conds);
> +	move_peer_conditionals(peer_conds);
> +
> +	if (mode_p) {
> +		mode = mode_p;
> +		if (mode & ~AA_VALID_NET_PERMS)
> +			yyerror("mode contains invalid permissions for unix socket rules\n");
> +		else if ((mode & AA_NET_BIND) &&
> +			 ((mode & AA_PEER_NET_PERMS) || has_peer_conds()))
> +			/* Do we want to loosen this? */
> +			yyerror("unix socket 'bind' access cannot be used with message rule conditionals\n");
> +		else if ((mode & AA_NET_LISTEN) &&
> +			 ((mode & AA_PEER_NET_PERMS) || has_peer_conds()))
> +			/* Do we want to loosen this? */
> +			yyerror("unix socket 'listen' access cannot be used with message rule conditionals\n");
> +		else if ((mode & AA_NET_ACCEPT) &&
> +			 ((mode & AA_PEER_NET_PERMS) || has_peer_conds()))
> +			/* Do we want to loosen this? */
> +			yyerror("unix socket 'accept' access cannot be used with message rule conditionals\n");
> +	} else {
> +		mode = AA_VALID_NET_PERMS;
> +	}
> +
> +	free_cond_list(conds);
> +	free_cond_list(peer_conds);
> +
> +}
> +
> +ostream &unix_rule::dump_local(ostream &os)
> +{
> +	af_rule::dump_local(os);
> +	if (path)
> +		os << "path='" << path << "'";
> +	return os;
> +}
> +
> +ostream &unix_rule::dump_peer(ostream &os)
> +{
> +	af_rule::dump_peer(os);
> +	if (peer_path)
> +		os << "path='" << peer_path << "'";
> +	return os;
> +}
> +
> +
> +int unix_rule::expand_variables(void)
> +{
> +	int error = af_rule::expand_variables();
> +	if (error)
> +		return error;
> +	error = expand_entry_variables(&path);
> +	if (error)
> +		return error;
> +	error = expand_entry_variables(&peer_path);
> +	if (error)
> +		return error;
> +
> +	return 0;
> +}
> +
> +/* do we want to warn once/profile or just once per compile?? */
> +static void warn_once(const char *name, const char *msg)
> +{
> +	static const char *warned_name = NULL;
> +
> +	if (warned_name != name) {
> +		cerr << "Warning from profile " << name << " (";
> +		if (current_filename)
> +			cerr << current_filename;
> +		else
> +			cerr << "stdin";
> +		cerr << "): " << msg << "\n";
> +		warned_name = name;
> +	}
> +}
> +
> +static void warn_once(const char *name)
> +{
> +	warn_once(name, "extended network unix socket rules not enforced");
> +}
> +
> +std::ostringstream &writeu16(std::ostringstream &o, int v)
> +{
> +	u16 tmp = htobe16((u16) v);
> +	char *c = (char *) &tmp;
> +	o << "\\x" << std::setfill('0') << std::setw(2) << std::hex << *c++;
> +	o << "\\x" << std::setfill('0') << std::setw(2) << std::hex << *c;
> +	return o;
> +}
> +
> +#define CMD_ADDR	1
> +#define CMD_LISTEN	2
> +#define CMD_ACCEPT	3
> +#define CMD_OPT		4
> +
> +void unix_rule::downgrade_rule(Profile &prof) {
> +	if (!prof.net.allow && !prof.alloc_net_table())
> +		yyerror(_("Memory allocation error."));
> +	if (deny) {
> +		prof.net.deny[AF_UNIX] |= 1 << sock_type_n;
> +		if (!audit)
> +			prof.net.quiet[AF_UNIX] |= 1 << sock_type_n;
> +	} else {
> +		prof.net.allow[AF_UNIX] |= 1 << sock_type_n;
> +		if (audit)
> +			prof.net.audit[AF_UNIX] |= 1 << sock_type_n;
> +	}
> +}
> +
> +int unix_rule::gen_policy_re(Profile &prof)
> +{
> +	std::ostringstream buffer, tmp;
> +	std::string buf;
> +
> +	pattern_t ptype;
> +	int pos;
> +	int mask = mode;
> +
> +	/* always generate a downgraded rule. This doesn't change generated
> +	 * policy size and allows the binary policy to be loaded against
> +	 * older kernels and be enforced to the best of the old network
> +	 * rules ability
> +	 */
> +	downgrade_rule(prof);
> +	if (!kernel_supports_unix) {
> +		if (kernel_supports_network) {
> +			/* only warn if we are building against a kernel
> +			 * that requires downgrading */
> +			warn_once(prof.name, "downgrading extended network unix socket rule to generic network rule\n");
> +			/* TODO: add ability to abort instead of downgrade */
> +			return RULE_OK;
> +		}
> +		warn_once(prof.name);
> +		return RULE_NOT_SUPPORTED;
> +	}
> +
> +
> +	buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_NET;
> +	buffer << writeu16(buffer, AF_UNIX);
> +	if (sock_type)
> +		buffer << writeu16(buffer, sock_type_n);
> +	else
> +		buffer << "..";
> +	if (proto)
> +		buffer << writeu16(buffer, proto_n);
> +	else
> +		buffer << "..";
> +
> +	if (mask & AA_NET_CREATE) {
> +		buf = buffer.str();
> +		if (!prof.policy.rules->add_rule(buf.c_str(), deny,
> +						 AA_NET_CREATE,
> +						 audit & AA_NET_CREATE,
> +						 dfaflags))
> +			goto fail;
> +		mask &= ~AA_NET_CREATE;
> +	}
> +
> +	/* local addr */
> +	if (path) {
> +		if (strcmp(path, "none") == 0) {
> +			buffer << "\\x01";
> +		} else {
> +			/* skip leading @ */
> +			ptype = convert_aaregex_to_pcre(path + 1, 0, buf, &pos);
> +			if (ptype == ePatternInvalid)
> +				goto fail;
> +			/* kernel starts abstract with \0 */
> +			buffer << "\\x00";
> +			buffer << buf;
> +		}
> +	} else
> +		buffer << ".*";
> +
> +	/* change to out of band separator */
> +	buffer << "\\x00";
> +
> +	if (mask & AA_LOCAL_NET_PERMS) {
> +		/* local label option */
> +		if (label) {
> +			ptype = convert_aaregex_to_pcre(label, 0, buf, &pos);
> +			if (ptype == ePatternInvalid)
> +				goto fail;
> +			/* kernel starts abstract with \0 */
> +			buffer << buf;
> +		} else
> +			tmp << anyone_match_pattern;
> +		buffer << "\\x00";
> +
> +		/* create already masked off */
> +		if (mask & AA_LOCAL_NET_PERMS & ~AA_LOCAL_NET_CMD) {
> +			buf = buffer.str();
> +			if (!prof.policy.rules->add_rule(buf.c_str(), deny,
> +							 mask & AA_LOCAL_NET_PERMS & ~AA_LOCAL_NET_CMD,
> +							 audit & AA_LOCAL_NET_PERMS & ~AA_LOCAL_NET_CMD,
> +							 dfaflags))
> +				goto fail;
> +		}
> +
> +		/* cmd selector - drop accept??? */
> +		if (mask & AA_NET_ACCEPT) {
> +			tmp.str(buffer.str());
> +			tmp << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_ACCEPT;
> +			buf = tmp.str();
> +			if (!prof.policy.rules->add_rule(buf.c_str(), deny,
> +							 AA_NET_ACCEPT,
> +							 audit & AA_NET_ACCEPT,
> +							 dfaflags))
> +				goto fail;
> +		}
> +		if (mask & AA_NET_LISTEN) {
> +			tmp.str(buffer.str());
> +			tmp << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_LISTEN;
> +			/* TODO: backlog conditional */
> +			tmp << "..";
> +			buf = tmp.str();
> +			if (!prof.policy.rules->add_rule(buf.c_str(), deny,
> +							 AA_NET_LISTEN,
> +							 audit & AA_NET_LISTEN,
> +							 dfaflags))
> +				goto fail;
> +		}
> +		if (mask & AA_NET_OPT) {
> +			tmp.str(buffer.str());
> +			tmp << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_OPT;
> +			/* TODO: sockopt conditional */
> +			tmp << "..";
> +			buf = tmp.str();
> +			if (!prof.policy.rules->add_rule(buf.c_str(), deny,
> +							 AA_NET_OPT,
> +							 audit & AA_NET_OPT,
> +							 dfaflags))
> +				goto fail;
> +		}
> +		mask &= ~AA_LOCAL_NET_PERMS;
> +	}
> +
> +	if (mask & AA_PEER_NET_PERMS) {
> +		/* cmd selector */
> +		buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_ADDR;
> +
> +		/* peer addr */
> +		if (peer_path) {
> +			if (strcmp(path, "none") == 0) {
> +				buffer << "\\x01";

The argument to strcmp should be peer_path, no? (Thank you, clang's
scan-build.)

> +			} else {
> +				/* skip leading @ */
> +				ptype = convert_aaregex_to_pcre(peer_path + 1, 0, buf, &pos);
> +				if (ptype == ePatternInvalid)
> +					goto fail;
> +				/* kernel starts abstract with \0 */
> +				buffer << "\\x00";
> +				buffer << buf;
> +			}
> +		}
> +		/* change to out of band separator */
> +		buffer << "\\x00";
> +
> +		if (peer_label) {
> +			ptype = convert_aaregex_to_pcre(peer_label, 0, buf, &pos);
> +			if (ptype == ePatternInvalid)
> +				goto fail;
> +			buffer << buf;
> +		} else {
> +			buffer << anyone_match_pattern;
> +		}
> +
> +		buf = buffer.str();
> +		if (!prof.policy.rules->add_rule(buf.c_str(), deny, mode & AA_PEER_NET_PERMS, audit, dfaflags))
> +			goto fail;
> +	}
> +
> +	return RULE_OK;
> +
> +fail:
> +	return RULE_ERROR;
> +}

-- 
Steve Beattie
<sbeattie at ubuntu.com>
http://NxNW.org/~steve/
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <https://lists.ubuntu.com/archives/apparmor/attachments/20140820/49e3e9a7/attachment-0001.pgp>


More information about the AppArmor mailing list