mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 16:35:02 +01:00

Add support for a default_allow mode that facillitates writing profiles in that allow everything by default. This is not normally recomended but fascilitates creating basic profiles while working to transition policy away from unconfined. This mode is being added specifically to replace the use of the unconfined flag in these transitional profiles as the use of unconfined in policy is confusing and does not reflect the semantics of what is being done. Generally the goal for policy should be to remove all default_allow profiles once the policy is fully developed. Note: this patch only adds parsing of default_allow mode. Currently it sets the unconfined flag to achieve default allow but this prevents deny rules from being applied. Once dominance is fixed a subsequent patch will transition default_allow away from using the unconfined flag. Signed-off-by: John Johansen <john.johansen@canonical.com>
442 lines
10 KiB
C++
442 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2012
|
|
* 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.
|
|
*/
|
|
#ifndef __AA_PROFILE_H
|
|
#define __AA_PROFILE_H
|
|
|
|
#include <set>
|
|
#include <string>
|
|
#include <iostream>
|
|
|
|
#include "capability.h"
|
|
#include "parser.h"
|
|
#include "rule.h"
|
|
#include "libapparmor_re/aare_rules.h"
|
|
#include "network.h"
|
|
#include "signal.h"
|
|
|
|
class Profile;
|
|
|
|
class block {
|
|
public:
|
|
|
|
};
|
|
|
|
|
|
struct deref_profileptr_lt {
|
|
bool operator()(Profile * const &lhs, Profile * const &rhs) const;
|
|
};
|
|
|
|
class ProfileList {
|
|
public:
|
|
set<Profile *, deref_profileptr_lt> list;
|
|
|
|
typedef set<Profile *, deref_profileptr_lt>::iterator iterator;
|
|
iterator begin() { return list.begin(); }
|
|
iterator end() { return list.end(); }
|
|
|
|
ProfileList() { };
|
|
virtual ~ProfileList() { clear(); }
|
|
virtual bool empty(void) { return list.empty(); }
|
|
virtual pair<ProfileList::iterator,bool> insert(Profile *);
|
|
virtual void erase(ProfileList::iterator pos);
|
|
void clear(void);
|
|
void dump(void);
|
|
void dump_profile_names(bool children);
|
|
};
|
|
|
|
extern const char*profile_mode_table[];
|
|
/* use profile_mode_packed to convert to the packed representation */
|
|
enum profile_mode {
|
|
MODE_UNSPECIFIED = 0,
|
|
MODE_ENFORCE = 1,
|
|
MODE_COMPLAIN = 2,
|
|
MODE_KILL = 3,
|
|
MODE_UNCONFINED = 4,
|
|
MODE_PROMPT = 5,
|
|
MODE_DEFAULT_ALLOW = 6,
|
|
MODE_CONFLICT = 7 /* greater than MODE_LAST */
|
|
};
|
|
#define MODE_LAST MODE_DEFAULT_ALLOW
|
|
|
|
static inline enum profile_mode operator++(enum profile_mode &mode)
|
|
{
|
|
mode = (enum profile_mode)((int) mode + 1);
|
|
return mode;
|
|
}
|
|
|
|
static inline enum profile_mode merge_profile_mode(enum profile_mode l, enum profile_mode r)
|
|
{
|
|
if (l == r || r == MODE_UNSPECIFIED)
|
|
return l;
|
|
else if (l == MODE_UNSPECIFIED)
|
|
return r;
|
|
return MODE_CONFLICT;
|
|
}
|
|
|
|
static inline uint32_t profile_mode_packed(enum profile_mode mode)
|
|
{
|
|
/* until dominance is fixed use unconfined mode for default_allow */
|
|
if (mode == MODE_DEFAULT_ALLOW)
|
|
mode = MODE_UNCONFINED;
|
|
/* kernel doesn't have an unspecified mode everything
|
|
* shifts down by 1
|
|
*/
|
|
if ((uint32_t) mode)
|
|
return (uint32_t) mode - 1;
|
|
/* unspecified defaults to same as enforce */
|
|
return 0;
|
|
}
|
|
|
|
static inline void mode_dump(ostream &os, enum profile_mode mode)
|
|
{
|
|
if (mode <= MODE_LAST)
|
|
os << profile_mode_table[(int) mode];
|
|
else
|
|
os << "unknown";
|
|
}
|
|
|
|
static inline enum profile_mode str_to_mode(const char *str)
|
|
{
|
|
for (enum profile_mode i = MODE_ENFORCE; i <= MODE_LAST; ++i) {
|
|
if (strcmp(profile_mode_table[i], str) == 0)
|
|
return i;
|
|
}
|
|
|
|
return MODE_UNSPECIFIED;
|
|
};
|
|
|
|
#define FLAG_HAT 1
|
|
#define FLAG_DEBUG1 2
|
|
#define FLAG_DEBUG2 4
|
|
#define FLAG_INTERRUPTIBLE 8
|
|
|
|
/* sigh, used in parse union so needs trivial constructors. */
|
|
class flagvals {
|
|
public:
|
|
int flags;
|
|
enum profile_mode mode;
|
|
int audit;
|
|
int path;
|
|
char *disconnected_path;
|
|
int signal;
|
|
|
|
// stupid not constructor constructors
|
|
void init(void)
|
|
{
|
|
flags = 0;
|
|
mode = MODE_UNSPECIFIED;
|
|
audit = 0;
|
|
path = 0;
|
|
disconnected_path = NULL;
|
|
signal = 0;
|
|
}
|
|
void init(const char *str)
|
|
{
|
|
init();
|
|
enum profile_mode pmode = str_to_mode(str);
|
|
|
|
if (strcmp(str, "debug") == 0) {
|
|
/* DEBUG2 is left for internal compiler use atm */
|
|
flags |= FLAG_DEBUG1;
|
|
} else if (pmode) {
|
|
mode = pmode;
|
|
} else if (strcmp(str, "audit") == 0) {
|
|
audit = 1;
|
|
} else if (strcmp(str, "chroot_relative") == 0) {
|
|
path |= PATH_CHROOT_REL;
|
|
} else if (strcmp(str, "namespace_relative") == 0) {
|
|
path |= PATH_NS_REL;
|
|
} else if (strcmp(str, "mediate_deleted") == 0) {
|
|
path |= PATH_MEDIATE_DELETED;
|
|
} else if (strcmp(str, "delegate_deleted") == 0) {
|
|
path |= PATH_DELEGATE_DELETED;
|
|
} else if (strcmp(str, "attach_disconnected") == 0) {
|
|
path |= PATH_ATTACH;
|
|
} else if (strcmp(str, "no_attach_disconnected") == 0) {
|
|
path |= PATH_NO_ATTACH;
|
|
} else if (strcmp(str, "chroot_attach") == 0) {
|
|
path |= PATH_CHROOT_NSATTACH;
|
|
} else if (strcmp(str, "chroot_no_attach") == 0) {
|
|
path |= PATH_CHROOT_NO_ATTACH;
|
|
} else if (strncmp(str, "attach_disconnected.path=", 25) == 0) {
|
|
/* TODO: make this a proper parse */
|
|
path |= PATH_ATTACH;
|
|
disconnected_path = strdup(str + 25);
|
|
} else if (strncmp(str, "kill.signal=", 12) == 0) {
|
|
/* TODO: make this a proper parse */
|
|
signal = find_signal_mapping(str + 12);
|
|
if (signal == -1)
|
|
yyerror("unknown signal specified for kill.signal=\'%s\'\n", str + 12);
|
|
} else if (strcmp(str, "interruptible") == 0) {
|
|
flags |= FLAG_INTERRUPTIBLE;
|
|
} else {
|
|
yyerror(_("Invalid profile flag: %s."), str);
|
|
}
|
|
}
|
|
|
|
ostream &dump(ostream &os)
|
|
{
|
|
os << "Mode: ";
|
|
mode_dump(os, mode);
|
|
if (audit)
|
|
os << ", Audit";
|
|
|
|
if (flags & FLAG_HAT)
|
|
os << ", Hat";
|
|
|
|
if (disconnected_path)
|
|
os << ", attach_disconnected.path=" << disconnected_path;
|
|
if (signal)
|
|
os << ", kill.signal=" << signal;
|
|
os << "\n";
|
|
|
|
return os;
|
|
}
|
|
ostream &debug(ostream &os)
|
|
{
|
|
#ifdef DEBUG
|
|
return dump(os);
|
|
#else
|
|
return os;
|
|
#endif
|
|
}
|
|
|
|
/* warning for now disconnected_path is just passed on (not copied),
|
|
* or leaked on error. It is not freed here, It is freed when the
|
|
* profile destroys it self.
|
|
*/
|
|
void merge(const flagvals &rhs)
|
|
{
|
|
if (merge_profile_mode(mode, rhs.mode) == MODE_CONFLICT)
|
|
yyerror(_("Profile flag '%s' conflicts with '%s'"),
|
|
profile_mode_table[mode],
|
|
profile_mode_table[rhs.mode]);
|
|
mode = merge_profile_mode(mode, rhs.mode);
|
|
audit = audit || rhs.audit;
|
|
path = path | rhs.path;
|
|
if ((path & (PATH_CHROOT_REL | PATH_NS_REL)) ==
|
|
(PATH_CHROOT_REL | PATH_NS_REL))
|
|
yyerror(_("Profile flag chroot_relative conflicts with namespace_relative"));
|
|
|
|
if ((path & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED)) ==
|
|
(PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))
|
|
yyerror(_("Profile flag mediate_deleted conflicts with delegate_deleted"));
|
|
if ((path & (PATH_ATTACH | PATH_NO_ATTACH)) ==
|
|
(PATH_ATTACH | PATH_NO_ATTACH))
|
|
yyerror(_("Profile flag attach_disconnected conflicts with no_attach_disconnected"));
|
|
if ((path & (PATH_CHROOT_NSATTACH | PATH_CHROOT_NO_ATTACH)) ==
|
|
(PATH_CHROOT_NSATTACH | PATH_CHROOT_NO_ATTACH))
|
|
yyerror(_("Profile flag chroot_attach conflicts with chroot_no_attach"));
|
|
|
|
if (rhs.disconnected_path) {
|
|
if (disconnected_path) {
|
|
if (strcmp(disconnected_path, rhs.disconnected_path) != 0) {
|
|
yyerror(_("Profile flag attach_disconnected set to conflicting values: '%s' and '%s'"), disconnected_path, rhs.disconnected_path);
|
|
}
|
|
// same ignore rhs.disconnect_path
|
|
} else {
|
|
disconnected_path = rhs.disconnected_path;
|
|
}
|
|
}
|
|
if (rhs.signal) {
|
|
if (signal) {
|
|
if (signal != rhs.signal) {
|
|
yyerror(_("Profile flag kill.signal set to conflicting values: '%d' and '%d'"), signal, rhs.signal);
|
|
}
|
|
// same so do nothing
|
|
} else {
|
|
signal = rhs.signal;
|
|
}
|
|
}
|
|
|
|
/* if we move to dupping disconnected_path will need to have
|
|
* an assignment and copy constructor and a destructor
|
|
*/
|
|
}
|
|
};
|
|
|
|
struct capabilities {
|
|
uint64_t allow;
|
|
uint64_t audit;
|
|
uint64_t deny;
|
|
uint64_t quiet;
|
|
|
|
capabilities(void) { allow = audit = deny = quiet = 0; }
|
|
|
|
void dump()
|
|
{
|
|
if (allow != 0ull)
|
|
__debug_capabilities(allow, "Capabilities");
|
|
if (audit != 0ull)
|
|
__debug_capabilities(audit, "Audit Caps");
|
|
if (deny != 0ull)
|
|
__debug_capabilities(deny, "Deny Caps");
|
|
if (quiet != 0ull)
|
|
__debug_capabilities(quiet, "Quiet Caps");
|
|
};
|
|
};
|
|
|
|
struct dfa_stuff {
|
|
aare_rules *rules;
|
|
void *dfa;
|
|
size_t size;
|
|
|
|
dfa_stuff(void): rules(NULL), dfa(NULL), size(0) { }
|
|
};
|
|
|
|
class Profile {
|
|
public:
|
|
char *ns;
|
|
char *name;
|
|
char *attachment;
|
|
struct alt_name *altnames;
|
|
void *xmatch;
|
|
size_t xmatch_size;
|
|
int xmatch_len;
|
|
|
|
struct cond_entry_list xattrs;
|
|
|
|
/* char *sub_name; */ /* subdomain name or NULL */
|
|
/* int default_deny; */ /* TRUE or FALSE */
|
|
bool local;
|
|
|
|
Profile *parent;
|
|
|
|
flagvals flags;
|
|
struct capabilities caps;
|
|
network_rule net;
|
|
|
|
struct aa_rlimits rlimits;
|
|
|
|
char *exec_table[AA_EXEC_COUNT];
|
|
struct cod_entry *entries;
|
|
RuleList rule_ents;
|
|
|
|
ProfileList hat_table;
|
|
|
|
struct dfa_stuff dfa;
|
|
struct dfa_stuff policy;
|
|
|
|
Profile(void)
|
|
{
|
|
ns = name = attachment = NULL;
|
|
altnames = NULL;
|
|
xmatch = NULL;
|
|
xmatch_size = 0;
|
|
xmatch_len = 0;
|
|
|
|
xattrs.list = NULL;
|
|
xattrs.name = NULL;
|
|
|
|
parent = NULL;
|
|
|
|
flags.init();
|
|
rlimits = {0, {}};
|
|
|
|
std::fill(exec_table, exec_table + AA_EXEC_COUNT, (char *)NULL);
|
|
|
|
entries = NULL;
|
|
};
|
|
|
|
virtual ~Profile();
|
|
|
|
bool operator<(Profile const &rhs)const
|
|
{
|
|
if (ns) {
|
|
if (rhs.ns) {
|
|
int res = strcmp(ns, rhs.ns);
|
|
if (res != 0)
|
|
return res < 0;
|
|
} else
|
|
return false;
|
|
} else if (rhs.ns)
|
|
return true;
|
|
return strcmp(name, rhs.name) < 0;
|
|
}
|
|
|
|
/*
|
|
* Requires the merged rules have customized methods
|
|
* cmp(), is_mergeable() and merge()
|
|
*/
|
|
virtual int merge_rules(void);
|
|
|
|
void dump(void)
|
|
{
|
|
if (ns)
|
|
printf("Ns:\t\t%s\n", ns);
|
|
|
|
if (name)
|
|
printf("Name:\t\t%s\n", name);
|
|
else
|
|
printf("Name:\t\t<NULL>\n");
|
|
|
|
if (parent)
|
|
printf("Local To:\t%s\n", parent->name);
|
|
else
|
|
printf("Local To:\t<NULL>\n");
|
|
|
|
flags.dump(cerr);
|
|
caps.dump();
|
|
|
|
if (entries)
|
|
debug_cod_entries(entries);
|
|
|
|
for (RuleList::iterator i = rule_ents.begin(); i != rule_ents.end(); i++) {
|
|
(*i)->dump(cout);
|
|
}
|
|
|
|
printf("\n");
|
|
hat_table.dump();
|
|
}
|
|
|
|
std::string hname(void)
|
|
{
|
|
if (!parent)
|
|
return name;
|
|
|
|
return parent->hname() + "//" + name;
|
|
}
|
|
|
|
/* assumes ns is set as part of profile creation */
|
|
std::string fqname(void)
|
|
{
|
|
if (parent)
|
|
return parent->fqname() + "//" + name;
|
|
else if (!ns)
|
|
return hname();
|
|
return ":" + std::string(ns) + "://" + hname();
|
|
}
|
|
|
|
std::string get_name(bool fqp)
|
|
{
|
|
if (fqp)
|
|
return fqname();
|
|
return hname();
|
|
}
|
|
|
|
void dump_name(bool fqp)
|
|
{
|
|
cout << get_name(fqp);;
|
|
}
|
|
|
|
void post_parse_profile(void);
|
|
void add_implied_rules(void);
|
|
|
|
protected:
|
|
const char *warned_name = NULL;
|
|
virtual void warn_once(const char *name, const char *msg);
|
|
};
|
|
|
|
|
|
#endif /* __AA_PROFILE_H */
|