2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 01:57:43 +00:00
apparmor/parser/dbus.c
John Johansen a066f80372 Convert mount and dbus to be subclasses of a generic rule class
This will simplify add new features as most of the code can reside in
its own class. There are still things to improve but its a start.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Steve Beattie <steve@nxnw.org>
2014-04-07 03:16:50 -07:00

411 lines
9.7 KiB
C

/*
* Copyright (c) 2013
* 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 "parser.h"
#include "profile.h"
#include "parser_yacc.h"
#include "dbus.h"
#define _(s) gettext(s)
static int parse_dbus_sub_mode(const char *str_mode, int *result, int fail, const char *mode_desc __unused)
{
int mode = 0;
const char *p;
PDEBUG("Parsing DBus mode: %s\n", str_mode);
if (!str_mode)
return 0;
p = str_mode;
while (*p) {
char current = *p;
char lower;
reeval:
switch (current) {
case COD_READ_CHAR:
PDEBUG("Parsing DBus mode: found %s READ\n", mode_desc);
mode |= AA_DBUS_RECEIVE;
break;
case COD_WRITE_CHAR:
PDEBUG("Parsing DBus mode: found %s WRITE\n",
mode_desc);
mode |= AA_DBUS_SEND;
break;
/* error cases */
default:
lower = tolower(current);
switch (lower) {
case COD_READ_CHAR:
case COD_WRITE_CHAR:
PDEBUG("Parsing DBus mode: found invalid upper case char %c\n",
current);
warn_uppercase();
current = lower;
goto reeval;
break;
default:
if (fail)
yyerror(_("Internal: unexpected DBus mode character '%c' in input"),
current);
else
return 0;
break;
}
break;
}
p++;
}
PDEBUG("Parsed DBus mode: %s 0x%x\n", str_mode, mode);
*result = mode;
return 1;
}
int parse_dbus_mode(const char *str_mode, int *mode, int fail)
{
*mode = 0;
if (!parse_dbus_sub_mode(str_mode, mode, fail, ""))
return 0;
if (*mode & ~AA_VALID_DBUS_PERMS) {
if (fail)
yyerror(_("Internal error generated invalid DBus perm 0x%x\n"),
mode);
else
return 0;
}
return 1;
}
static int list_len(struct value_list *v)
{
int len = 0;
struct value_list *tmp;
list_for_each(v, tmp)
len++;
return len;
}
static void move_conditional_value(char **dst_ptr, struct cond_entry *cond_ent)
{
if (*dst_ptr)
yyerror("dbus conditional \"%s\" can only be specified once\n",
cond_ent->name);
*dst_ptr = cond_ent->vals->value;
cond_ent->vals->value = NULL;
}
void dbus_rule::move_conditionals(struct cond_entry *conds)
{
struct cond_entry *cond_ent;
list_for_each(conds, cond_ent) {
/* for now disallow keyword 'in' (list) */
if (!cond_ent->eq)
yyerror("keyword \"in\" is not allowed in dbus rules\n");
if (list_len(cond_ent->vals) > 1)
yyerror("dbus conditional \"%s\" only supports a single value\n",
cond_ent->name);
if (strcmp(cond_ent->name, "bus") == 0) {
move_conditional_value(&bus, cond_ent);
} else if (strcmp(cond_ent->name, "name") == 0) {
move_conditional_value(&name, cond_ent);
} else if (strcmp(cond_ent->name, "label") == 0) {
move_conditional_value(&peer_label, cond_ent);
} else if (strcmp(cond_ent->name, "path") == 0) {
move_conditional_value(&path, cond_ent);
} else if (strcmp(cond_ent->name, "interface") == 0) {
move_conditional_value(&interface, cond_ent);
} else if (strcmp(cond_ent->name, "member") == 0) {
move_conditional_value(&member, cond_ent);
} else {
yyerror("invalid dbus conditional \"%s\"\n",
cond_ent->name);
}
}
}
dbus_rule::dbus_rule(int mode_p, struct cond_entry *conds,
struct cond_entry *peer_conds):
bus(NULL), name(NULL), peer_label(NULL), path(NULL), interface(NULL), member(NULL),
mode(0), audit(0), deny(0)
{
int name_is_subject_cond = 0, message_rule = 0, service_rule = 0;
/* Move the global/subject conditionals over & check the results */
move_conditionals(conds);
if (name)
name_is_subject_cond = 1;
if (peer_label)
yyerror("dbus \"label\" conditional can only be used inside of the \"peer=()\" grouping\n");
/* Move the peer conditionals */
move_conditionals(peer_conds);
if (path || interface || member || peer_label ||
(name && !name_is_subject_cond))
message_rule = 1;
if (name && name_is_subject_cond)
service_rule = 1;
if (message_rule && service_rule)
yyerror("dbus rule contains message conditionals and service conditionals\n");
/* Copy mode. If no mode was specified, assign an implied mode. */
if (mode_p) {
mode = mode_p;
if (mode & ~AA_VALID_DBUS_PERMS)
yyerror("mode contains unknown dbus accesss\n");
else if (message_rule && (mode & AA_DBUS_BIND))
yyerror("dbus \"bind\" access cannot be used with message rule conditionals\n");
else if (service_rule && (mode & (AA_DBUS_SEND | AA_DBUS_RECEIVE)))
yyerror("dbus \"send\" and/or \"receive\" accesses cannot be used with service rule conditionals\n");
else if (mode & AA_DBUS_EAVESDROP &&
(path || interface || member ||
peer_label || name)) {
yyerror("dbus \"eavesdrop\" access can only contain a bus conditional\n");
}
} else {
if (message_rule)
mode = (AA_DBUS_SEND | AA_DBUS_RECEIVE);
else if (service_rule)
mode = (AA_DBUS_BIND);
else
mode = AA_VALID_DBUS_PERMS;
}
free_cond_list(conds);
free_cond_list(peer_conds);
}
ostream &dbus_rule::dump(ostream &os)
{
if (audit)
os << "audit ";
if (deny)
os << "deny ";
os << "dbus ( ";
if (mode & AA_DBUS_SEND)
os << "send ";
if (mode & AA_DBUS_RECEIVE)
os << "receive ";
if (mode & AA_DBUS_BIND)
os << "bind ";
if (mode & AA_DBUS_EAVESDROP)
os << "eavesdrop ";
os << ")";
if (bus)
os << " bus=\"" << bus << "\"";
if ((mode & AA_DBUS_BIND) && name)
os << " name=\"" << name << "\"";
if (path)
os << " path=\"" << path << "\"";
if (interface)
os << " interface=\"" << interface << "\"";
if (member)
os << " member=\"" << member << os << "\"";
if (!(mode & AA_DBUS_BIND) && (peer_label || name)) {
os << " peer=( ";
if (peer_label)
os << "label=\"" << peer_label << "\" ";
if (name)
os << "name=\"" << name << "\" ";
os << ")";
}
os << ",\n";
return os;
}
int dbus_rule::expand_variables(void)
{
int error = expand_entry_variables(&bus);
if (error)
return error;
error = expand_entry_variables(&name);
if (error)
return error;
error = expand_entry_variables(&peer_label);
if (error)
return error;
error = expand_entry_variables(&path);
if (error)
return error;
error = expand_entry_variables(&interface);
if (error)
return error;
error = expand_entry_variables(&member);
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)
{
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 << ") dbus rules not enforced\n";
warned_name = name;
}
}
int dbus_rule::gen_policy_re(Profile &prof)
{
std::string busbuf;
std::string namebuf;
std::string peer_labelbuf;
std::string pathbuf;
std::string ifacebuf;
std::string memberbuf;
std::ostringstream buffer;
const char *vec[6];
pattern_t ptype;
int pos;
if (!kernel_supports_dbus) {
warn_once(prof.name);
return RULE_NOT_SUPPORTED;
}
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_DBUS;
busbuf.append(buffer.str());
if (bus) {
ptype = convert_aaregex_to_pcre(bus, 0, busbuf, &pos);
if (ptype == ePatternInvalid)
goto fail;
} else {
/* match any char except \000 0 or more times */
busbuf.append(default_match_pattern);
}
vec[0] = busbuf.c_str();
if (name) {
ptype = convert_aaregex_to_pcre(name, 0, namebuf, &pos);
if (ptype == ePatternInvalid)
goto fail;
vec[1] = namebuf.c_str();
} else {
/* match any char except \000 0 or more times */
vec[1] = default_match_pattern;
}
if (peer_label) {
ptype = convert_aaregex_to_pcre(peer_label, 0,
peer_labelbuf, &pos);
if (ptype == ePatternInvalid)
goto fail;
vec[2] = peer_labelbuf.c_str();
} else {
/* match any char except \000 0 or more times */
vec[2] = default_match_pattern;
}
if (path) {
ptype = convert_aaregex_to_pcre(path, 0, pathbuf, &pos);
if (ptype == ePatternInvalid)
goto fail;
vec[3] = pathbuf.c_str();
} else {
/* match any char except \000 0 or more times */
vec[3] = default_match_pattern;
}
if (interface) {
ptype = convert_aaregex_to_pcre(interface, 0, ifacebuf, &pos);
if (ptype == ePatternInvalid)
goto fail;
vec[4] = ifacebuf.c_str();
} else {
/* match any char except \000 0 or more times */
vec[4] = default_match_pattern;
}
if (member) {
ptype = convert_aaregex_to_pcre(member, 0, memberbuf, &pos);
if (ptype == ePatternInvalid)
goto fail;
vec[5] = memberbuf.c_str();
} else {
/* match any char except \000 0 or more times */
vec[5] = default_match_pattern;
}
if (mode & AA_DBUS_BIND) {
if (!aare_add_rule_vec(prof.policy.rules, deny,
mode & AA_DBUS_BIND,
audit & AA_DBUS_BIND,
2, vec, dfaflags))
goto fail;
}
if (mode & (AA_DBUS_SEND | AA_DBUS_RECEIVE)) {
if (!aare_add_rule_vec(prof.policy.rules, deny,
mode & (AA_DBUS_SEND | AA_DBUS_RECEIVE),
audit & (AA_DBUS_SEND | AA_DBUS_RECEIVE),
6, vec, dfaflags))
goto fail;
}
if (mode & AA_DBUS_EAVESDROP) {
if (!aare_add_rule_vec(prof.policy.rules, deny,
mode & AA_DBUS_EAVESDROP,
audit & AA_DBUS_EAVESDROP,
1, vec, dfaflags))
goto fail;
}
prof.policy.count++;
return RULE_OK;
fail:
return RULE_ERROR;
}