[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