mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 08:24:42 +01:00

Before 300889c3a
, mount rules would compile policy when using source
as mount point for rules that contain propagation type flags, such as
unbindable, runbindable, private, rprivate, slave, rslave, shared, and
rshared. Even though it compiled, the rule generated would not work as
expected.
This commit fixes both issues. It allows the usage of source as mount
point for the specified flags, albeit with a deprecation warning, and
it correctly generates the mount rule.
The policy fails to load when both source and mount point are
specified, keeping the original behavior (reference
parser/tst/simple_tests/mount/bad_opt_10.sd for example).
Fixes: https://bugs.launchpad.net/bugs/1648245
Fixes: https://bugs.launchpad.net/bugs/2023025
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
1117 lines
29 KiB
C++
1117 lines
29 KiB
C++
/*
|
|
* Copyright (c) 2010
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* The mount command, its mix of options and flags, its permissions and
|
|
* mapping are a mess.
|
|
* mount [-lhV]
|
|
*
|
|
* mount -a [-fFnrsvw] [-t vfstype] [-O optlist]
|
|
*
|
|
* mount [-fnrsvw] [-o option[,option]...] device|dir
|
|
*
|
|
* mount [-fnrsvw] [-t vfstype] [-o options] device dir
|
|
*
|
|
*----------------------------------------------------------------------
|
|
* Mount flags of no interest for apparmor mediation
|
|
* -a, --all
|
|
* -F fork for simultaneous mount
|
|
* -f fake, do everything except that actual system call
|
|
* -h --help
|
|
* -i, --internal-only
|
|
* -n mount without writing in /etc/mtab
|
|
* -O <optlist> limits what is auto mounted
|
|
* -p, --pass-fd num
|
|
* -s Tolerate sloppy mount options
|
|
* -U uuid
|
|
* -V --version
|
|
* --no-canonicalize
|
|
*
|
|
*----------------------------------------------------------------------
|
|
* what do we do with these
|
|
* -l list?
|
|
* -L <label> label
|
|
* -v --verbose deprecated
|
|
*
|
|
*----------------------------------------------------------------------
|
|
* Filesystem type
|
|
* -t <vfstype>
|
|
* vfstype=<vfstype>
|
|
*
|
|
*----------------------------------------------------------------------
|
|
* Mount Flags/options (-o --options)
|
|
* -o option[,option]
|
|
*
|
|
* The Linux kernel has 32 fs - independent mount flags, that mount command
|
|
* is responsible for stripping out and mapping to a 32 bit flags field.
|
|
* The mount commands mapping is documented below.
|
|
*
|
|
* Unfortunately we can not directly use this mapping as we need to be able
|
|
* represent, whether none, 1 or both options of a flag can be present for
|
|
* example
|
|
* ro, and rw information is stored in a single bit. But we need 2 bits
|
|
* of information.
|
|
* ro - the mount can only be readonly
|
|
* rw - the mount can only be rw
|
|
* ro/rw - the mount can be either ro/rw
|
|
* the fourth state of neither ro/rw does not exist, but still we need
|
|
* >1 bit to represent the possible choices
|
|
*
|
|
* The fs specific mount options are passed into the kernel as a string
|
|
* to be interpreted by the filesystem.
|
|
*
|
|
*
|
|
* #define MS_RDONLY 1 Mount read-only
|
|
* ro -r --read-only [source] dest
|
|
* rw -w
|
|
* #define MS_NOSUID 2 Ignore suid and sgid bits
|
|
* nosuid
|
|
* suid
|
|
* #define MS_NODEV 4 Disallow access to device special files
|
|
* nodev
|
|
* dev
|
|
* #define MS_NOEXEC 8 Disallow program execution
|
|
* noexec
|
|
* exec
|
|
* #define MS_SYNCHRONOUS 16 Writes are synced at once
|
|
* sync
|
|
* async
|
|
* #define MS_REMOUNT 32 Alter flags of a mounted FS
|
|
* remount source dest
|
|
* #define MS_MANDLOCK 64 Allow mandatory locks on an FS
|
|
* mand
|
|
* nomand
|
|
* #define MS_DIRSYNC 128 Directory modifications are synchronous
|
|
* dirsync
|
|
* #define MS_NOSYMFOLLOW 256 Do not follow symlinks
|
|
* symfollow
|
|
* nosymfollow
|
|
* #define MS_NOATIME 1024 Do not update access times
|
|
* noatime
|
|
* atime
|
|
* #define MS_NODIRATIME 2048 Do not update directory access times
|
|
* nodiratime
|
|
* diratime
|
|
* #define MS_BIND 4096
|
|
* --bind -B source dest
|
|
* #define MS_MOVE 8192
|
|
* --move -M source dest
|
|
* #define MS_REC 16384
|
|
* --rbind -R source dest
|
|
* --make-rshared dest
|
|
* --make-rslave dest
|
|
* --make-rprivate dest
|
|
* --make-runbindable dest
|
|
* #define MS_VERBOSE 32768 MS_VERBOSE is deprecated
|
|
* #define MS_SILENT 32768
|
|
* silent
|
|
* load
|
|
* #define MS_POSIXACL (1<<16) VFS does not apply the umask
|
|
* acl
|
|
* noacl
|
|
* #define MS_UNBINDABLE (1<<17) change to unbindable
|
|
* --make-unbindable dest
|
|
* #define MS_PRIVATE (1<<18) change to private
|
|
* --make-private dest
|
|
* #define MS_SLAVE (1<<19) change to slave
|
|
* --make-slave dest
|
|
* #define MS_SHARED (1<<20) change to shared
|
|
* --make-shared dest
|
|
* #define MS_RELATIME (1<<21) Update atime relative to mtime/ctime
|
|
* relatime
|
|
* norelatime
|
|
* #define MS_KERNMOUNT (1<<22) this is a kern_mount call
|
|
* #define MS_I_VERSION (1<<23) Update inode I_version field
|
|
* iversion
|
|
* noiversion
|
|
* #define MS_STRICTATIME (1<<24) Always perform atime updates
|
|
* strictatime
|
|
* nostrictatime
|
|
* #define MS_LAZYTIME (1<<25) Update the on-disk [acm]times lazily
|
|
* lazytime
|
|
* nolazytime
|
|
* #define MS_NOSEC (1<<28)
|
|
* #define MS_BORN (1<<29)
|
|
* #define MS_ACTIVE (1<<30)
|
|
* #define MS_NOUSER (1<<31)
|
|
* nouser
|
|
* user
|
|
*
|
|
* other mount options of interest
|
|
*
|
|
* selinux
|
|
* context=<context>
|
|
* fscontext=<context>
|
|
* defcontext=<context>,
|
|
* rootcontext=<context>
|
|
*
|
|
* defaults -> rw, suid, dev, exec, auto, nouser, async
|
|
* owner -> implies nosuid and nodev
|
|
* users -> implies noexec, nosuid, and nodev
|
|
*
|
|
*----------------------------------------------------------------------
|
|
* AppArmor mount rules
|
|
*
|
|
* AppArmor mount rules try to leverage mount syntax within apparmor syntax
|
|
* this can not be done entirely but it is largely covered.
|
|
*
|
|
* The general mount syntax is
|
|
* [audit] [deny] [owner] mount [conds]* [source] [ -> [conds] path],
|
|
* [audit] [deny] remount [conds]* [path],
|
|
* [audit] [deny] umount [conds]* [path],
|
|
*
|
|
* Note: leading owner option applies owner condition to both sours and dest
|
|
* path.
|
|
*
|
|
* where [conds] can be
|
|
* fstype=<expr>
|
|
* options=<expr>
|
|
* owner[=<expr>]
|
|
*
|
|
* <expr> := <re> | '(' (<re>[,])+ ')'
|
|
*
|
|
* If a condition is not specified then it is assumed to match all possible
|
|
* entries for it. ie. a missing fstype means all fstypes are matched.
|
|
* However if a condition is specified then the rule only grants permission
|
|
* for mounts matching the specified pattern.
|
|
*
|
|
* Examples.
|
|
* mount, # allow any mount
|
|
* mount /dev/foo, # allow mounting of /dev/foo anywhere
|
|
* mount options=ro /dev/foo, #allow mounting /dev/foo as read only
|
|
* mount options=(ro,foo) /dev/foo,
|
|
* mount options=ro options=foo /dev/foo,
|
|
* mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) overlay -> /mnt/
|
|
*
|
|
*----------------------------------------------------------------------
|
|
* pivotroot
|
|
* pivotroot [oldroot=<value>] <path> -> <profile>
|
|
* pivotroot <path> -> { }
|
|
*
|
|
*----------------------------------------------------------------------
|
|
* chroot
|
|
* chroot <path> -> <profile>
|
|
* chroot <path> -> { }
|
|
*
|
|
*----------------------------------------------------------------------
|
|
* AppArmor mount rule encoding
|
|
*
|
|
* TODO:
|
|
* add semantic checking of options against specified filesystem types
|
|
* to catch mount options that can't be covered.
|
|
*
|
|
*
|
|
*/
|
|
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <linux/limits.h>
|
|
|
|
#include "parser.h"
|
|
#include "policydb.h"
|
|
#include "profile.h"
|
|
#include "mount.h"
|
|
|
|
struct mnt_keyword_table {
|
|
const char *keyword;
|
|
unsigned int set;
|
|
unsigned int clear;
|
|
};
|
|
|
|
static struct mnt_keyword_table mnt_opts_table[] = {
|
|
{"ro", MS_RDONLY, 0},
|
|
{"r", MS_RDONLY, 0},
|
|
{"read-only", MS_RDONLY, 0},
|
|
{"rw", 0, MS_RDONLY},
|
|
{"w", 0, MS_RDONLY},
|
|
{"suid", 0, MS_NOSUID},
|
|
{"nosuid", MS_NOSUID, 0},
|
|
{"dev", 0, MS_NODEV},
|
|
{"nodev", MS_NODEV, 0},
|
|
{"exec", 0, MS_NOEXEC},
|
|
{"noexec", MS_NOEXEC, 0},
|
|
{"sync", MS_SYNC, 0},
|
|
{"async", 0, MS_SYNC},
|
|
{"remount", MS_REMOUNT, 0},
|
|
{"mand", MS_MAND, 0},
|
|
{"nomand", 0, MS_MAND},
|
|
{"dirsync", MS_DIRSYNC, 0},
|
|
{"symfollow", 0, MS_NOSYMFOLLOW},
|
|
{"nosymfollow", MS_NOSYMFOLLOW, 0},
|
|
{"atime", 0, MS_NOATIME},
|
|
{"noatime", MS_NOATIME, 0},
|
|
{"diratime", 0, MS_NODIRATIME},
|
|
{"nodiratime", MS_NODIRATIME, 0},
|
|
{"bind", MS_BIND, 0},
|
|
{"B", MS_BIND, 0},
|
|
{"move", MS_MOVE, 0},
|
|
{"M", MS_MOVE, 0},
|
|
{"rbind", MS_RBIND, 0},
|
|
{"R", MS_RBIND, 0},
|
|
{"verbose", MS_VERBOSE, 0},
|
|
{"silent", MS_SILENT, 0},
|
|
{"loud", 0, MS_SILENT},
|
|
{"acl", MS_ACL, 0},
|
|
{"noacl", 0, MS_ACL},
|
|
{"unbindable", MS_UNBINDABLE, 0},
|
|
{"make-unbindable", MS_UNBINDABLE, 0},
|
|
{"runbindable", MS_RUNBINDABLE, 0},
|
|
{"make-runbindable", MS_RUNBINDABLE, 0},
|
|
{"private", MS_PRIVATE, 0},
|
|
{"make-private", MS_PRIVATE, 0},
|
|
{"rprivate", MS_RPRIVATE, 0},
|
|
{"make-rprivate", MS_RPRIVATE, 0},
|
|
{"slave", MS_SLAVE, 0},
|
|
{"make-slave", MS_SLAVE, 0},
|
|
{"rslave", MS_RSLAVE, 0},
|
|
{"make-rslave", MS_RSLAVE, 0},
|
|
{"shared", MS_SHARED, 0},
|
|
{"make-shared", MS_SHARED, 0},
|
|
{"rshared", MS_RSHARED, 0},
|
|
{"make-rshared", MS_RSHARED, 0},
|
|
|
|
{"relatime", MS_RELATIME, 0},
|
|
{"norelatime", 0, MS_NORELATIME},
|
|
{"iversion", MS_IVERSION, 0},
|
|
{"noiversion", 0, MS_IVERSION},
|
|
{"strictatime", MS_STRICTATIME, 0},
|
|
{"nostrictatime", 0, MS_STRICTATIME},
|
|
{"lazytime", MS_LAZYTIME, 0},
|
|
{"nolazytime", 0, MS_LAZYTIME},
|
|
{"user", 0, (unsigned int) MS_NOUSER},
|
|
{"nouser", (unsigned int) MS_NOUSER, 0},
|
|
|
|
{NULL, 0, 0}
|
|
};
|
|
|
|
static struct mnt_keyword_table mnt_conds_table[] = {
|
|
{"options", MNT_SRC_OPT, MNT_COND_OPTIONS},
|
|
{"option", MNT_SRC_OPT, MNT_COND_OPTIONS},
|
|
{"fstype", MNT_SRC_OPT | MNT_DST_OPT, MNT_COND_FSTYPE},
|
|
{"vfstype", MNT_SRC_OPT | MNT_DST_OPT, MNT_COND_FSTYPE},
|
|
|
|
{NULL, 0, 0}
|
|
};
|
|
|
|
static ostream &dump_flags(ostream &os,
|
|
pair <unsigned int, unsigned int> flags)
|
|
{
|
|
for (int i = 0; mnt_opts_table[i].keyword; i++) {
|
|
if ((flags.first & mnt_opts_table[i].set) ||
|
|
(flags.second & mnt_opts_table[i].clear))
|
|
os << mnt_opts_table[i].keyword;
|
|
}
|
|
return os;
|
|
}
|
|
|
|
ostream &operator<<(ostream &os, pair<unsigned int, unsigned int> flags)
|
|
{
|
|
return dump_flags(os, flags);
|
|
}
|
|
|
|
static int find_mnt_keyword(struct mnt_keyword_table *table, const char *name)
|
|
{
|
|
int i;
|
|
for (i = 0; table[i].keyword; i++) {
|
|
if (strcmp(name, table[i].keyword) == 0)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int is_valid_mnt_cond(const char *name, int src)
|
|
{
|
|
int i;
|
|
i = find_mnt_keyword(mnt_conds_table, name);
|
|
if (i != -1)
|
|
return (mnt_conds_table[i].set & src);
|
|
return -1;
|
|
}
|
|
|
|
static unsigned int extract_flags(struct value_list **list, unsigned int *inv)
|
|
{
|
|
unsigned int flags = 0, invflags = 0;
|
|
*inv = 0;
|
|
|
|
struct value_list *entry, *tmp, *prev = NULL;
|
|
list_for_each_safe(*list, entry, tmp) {
|
|
int i;
|
|
i = find_mnt_keyword(mnt_opts_table, entry->value);
|
|
if (i != -1) {
|
|
flags |= mnt_opts_table[i].set;
|
|
invflags |= mnt_opts_table[i].clear;
|
|
PDEBUG(" extracting mount flag %s req: 0x%x inv: 0x%x"
|
|
" => req: 0x%x inv: 0x%x\n",
|
|
entry->value, mnt_opts_table[i].set,
|
|
mnt_opts_table[i].clear, flags, invflags);
|
|
if (prev)
|
|
prev->next = tmp;
|
|
if (entry == *list)
|
|
*list = tmp;
|
|
entry->next = NULL;
|
|
free_value_list(entry);
|
|
} else
|
|
prev = entry;
|
|
}
|
|
|
|
if (inv)
|
|
*inv = invflags;
|
|
|
|
return flags;
|
|
}
|
|
|
|
static bool conflicting_flags(unsigned int flags, unsigned int inv)
|
|
{
|
|
if (flags & inv) {
|
|
for (int i = 0; i < 31; i++) {
|
|
unsigned int mask = 1 << i;
|
|
if ((flags & inv) & mask) {
|
|
cerr << "conflicting flag values = "
|
|
<< flags << ", " << inv << "\n";
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static struct value_list *extract_fstype(struct cond_entry **conds)
|
|
{
|
|
struct value_list *list = NULL;
|
|
|
|
struct cond_entry *entry, *tmp, *prev = NULL;
|
|
|
|
list_for_each_safe(*conds, entry, tmp) {
|
|
if (strcmp(entry->name, "fstype") == 0 ||
|
|
strcmp(entry->name, "vfstype") == 0) {
|
|
PDEBUG(" extracting fstype\n");
|
|
list_remove_at(*conds, prev, entry);
|
|
list_append(entry->vals, list);
|
|
list = entry->vals;
|
|
entry->vals = NULL;
|
|
free_cond_entry(entry);
|
|
} else
|
|
prev = entry;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
static struct cond_entry *extract_options(struct cond_entry **conds, int eq)
|
|
{
|
|
struct cond_entry *list = NULL, *entry, *tmp, *prev = NULL;
|
|
|
|
list_for_each_safe(*conds, entry, tmp) {
|
|
if ((strcmp(entry->name, "options") == 0 ||
|
|
strcmp(entry->name, "option") == 0) &&
|
|
entry->eq == eq) {
|
|
list_remove_at(*conds, prev, entry);
|
|
PDEBUG(" extracting %s %s\n", entry->name, entry->eq ?
|
|
"=" : "in");
|
|
list_append(entry, list);
|
|
list = entry;
|
|
} else
|
|
prev = entry;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
static void perror_conds(const char *rule, struct cond_entry *conds)
|
|
{
|
|
struct cond_entry *entry;
|
|
|
|
list_for_each(conds, entry) {
|
|
PERROR( "unsupported %s condition '%s%s(...)'\n", rule, entry->name, entry->eq ? "=" : " in ");
|
|
}
|
|
}
|
|
|
|
static void perror_vals(const char *rule, struct value_list *vals)
|
|
{
|
|
struct value_list *entry;
|
|
|
|
list_for_each(vals, entry) {
|
|
PERROR( "unsupported %s value '%s'\n", rule, entry->value);
|
|
}
|
|
}
|
|
|
|
static void process_one_option(struct cond_entry *&opts, unsigned int &flags,
|
|
unsigned int &inv_flags)
|
|
{
|
|
struct cond_entry *entry;
|
|
struct value_list *vals;
|
|
|
|
entry = list_pop(opts);
|
|
vals = entry->vals;
|
|
entry->vals = NULL;
|
|
/* fail if there are any unknown optional flags */
|
|
if (opts) {
|
|
PERROR(" unsupported multiple 'mount options %s(...)'\n", entry->eq ? "=" : " in ");
|
|
exit(1);
|
|
}
|
|
free_cond_entry(entry);
|
|
|
|
flags = extract_flags(&vals, &inv_flags);
|
|
if (vals) {
|
|
perror_vals("mount option", vals);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
mnt_rule::mnt_rule(struct cond_entry *src_conds, char *device_p,
|
|
struct cond_entry *dst_conds unused, char *mnt_point_p,
|
|
perms_t perms_p):
|
|
perms_rule_t(AA_CLASS_MOUNT),
|
|
mnt_point(mnt_point_p), device(device_p), trans(NULL), opts(NULL),
|
|
flagsv(0), opt_flagsv(0)
|
|
{
|
|
/* FIXME: dst_conds are ignored atm */
|
|
dev_type = extract_fstype(&src_conds);
|
|
|
|
if (src_conds) {
|
|
/* move options in () to local list */
|
|
struct cond_entry *opts_in = extract_options(&src_conds, 0);
|
|
|
|
if (opts_in) {
|
|
unsigned int tmpflags = 0, tmpinv_flags = 0;
|
|
struct cond_entry *entry;
|
|
|
|
while ((entry = list_pop(opts_in))) {
|
|
process_one_option(entry, tmpflags,
|
|
tmpinv_flags);
|
|
/* optional flags if set/clear mean the same
|
|
* thing and can be represented by a single
|
|
* bitset, also there is no need to check for
|
|
* conflicting flags when they are optional
|
|
*/
|
|
opt_flagsv.push_back(tmpflags | tmpinv_flags);
|
|
}
|
|
}
|
|
|
|
/* move options=() to opts list */
|
|
struct cond_entry *opts_eq = extract_options(&src_conds, 1);
|
|
if (opts_eq) {
|
|
unsigned int tmpflags = 0, tmpinv_flags = 0;
|
|
struct cond_entry *entry;
|
|
|
|
while ((entry = list_pop(opts_eq))) {
|
|
process_one_option(entry, tmpflags,
|
|
tmpinv_flags);
|
|
/* throw away tmpinv_flags, only needed in
|
|
* consistancy check
|
|
*/
|
|
if (perms_p & AA_DUMMY_REMOUNT)
|
|
tmpflags |= MS_REMOUNT;
|
|
|
|
if (conflicting_flags(tmpflags, tmpinv_flags)) {
|
|
PERROR("conflicting flags in the rule\n");
|
|
exit(1);
|
|
}
|
|
|
|
flagsv.push_back(tmpflags);
|
|
}
|
|
}
|
|
|
|
if (src_conds) {
|
|
perror_conds("mount", src_conds);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (!(flagsv.size() + opt_flagsv.size())) {
|
|
/* no flag options, and not remount, allow everything */
|
|
if (perms_p & AA_DUMMY_REMOUNT) {
|
|
flagsv.push_back(MS_REMOUNT);
|
|
opt_flagsv.push_back(MS_REMOUNT_FLAGS & ~MS_REMOUNT);
|
|
} else {
|
|
flagsv.push_back(MS_ALL_FLAGS);
|
|
opt_flagsv.push_back(MS_ALL_FLAGS);
|
|
}
|
|
} else if (!(flagsv.size())) {
|
|
/* no flags but opts set */
|
|
if (perms_p & AA_DUMMY_REMOUNT)
|
|
flagsv.push_back(MS_REMOUNT);
|
|
else
|
|
flagsv.push_back(0);
|
|
} else if (!(opt_flagsv.size())) {
|
|
opt_flagsv.push_back(0);
|
|
}
|
|
|
|
if (perms_p & AA_DUMMY_REMOUNT) {
|
|
perms_p = AA_MAY_MOUNT;
|
|
}
|
|
perms = perms_p;
|
|
}
|
|
|
|
ostream &mnt_rule::dump(ostream &os)
|
|
{
|
|
prefix_rule_t::dump(os);
|
|
|
|
if (perms & AA_MAY_MOUNT)
|
|
os << "mount";
|
|
else if (perms & AA_MAY_UMOUNT)
|
|
os << "umount";
|
|
else if (perms & AA_MAY_PIVOTROOT)
|
|
os << "pivotroot";
|
|
else
|
|
os << "error: unknown mount perm";
|
|
|
|
for (unsigned int i = 0; i < flagsv.size(); i++)
|
|
os << " flags=(0x" << hex << flagsv[i] << ")";
|
|
for (unsigned int i = 0; i < opt_flagsv.size(); i++)
|
|
os << " flags in (0x" << hex << opt_flagsv[i] << ")";
|
|
|
|
if (dev_type) {
|
|
os << " type=";
|
|
print_value_list(dev_type);
|
|
}
|
|
if (opts) {
|
|
os << " options=";
|
|
print_value_list(opts);
|
|
}
|
|
if (device)
|
|
os << " " << device;
|
|
if (mnt_point)
|
|
os << " -> " << mnt_point;
|
|
if (trans)
|
|
os << " -> " << trans;
|
|
|
|
|
|
os << " " << "(0x" << hex << perms << "/0x" << (audit != AUDIT_UNSPECIFIED ? perms : 0) << ")";
|
|
os << ",\n";
|
|
|
|
return os;
|
|
}
|
|
|
|
/* does not currently support expansion of vars in options */
|
|
int mnt_rule::expand_variables(void)
|
|
{
|
|
struct value_list *ent;
|
|
int error = 0;
|
|
|
|
error = expand_entry_variables(&mnt_point);
|
|
if (error)
|
|
return error;
|
|
filter_slashes(mnt_point);
|
|
error = expand_entry_variables(&device);
|
|
if (error)
|
|
return error;
|
|
filter_slashes(device);
|
|
error = expand_entry_variables(&trans);
|
|
if (error)
|
|
return error;
|
|
|
|
list_for_each(dev_type, ent) {
|
|
error = expand_entry_variables(&ent->value);
|
|
if (error)
|
|
return error;
|
|
}
|
|
list_for_each(opts, ent) {
|
|
error = expand_entry_variables(&ent->value);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int build_mnt_flags(char *buffer, int size, unsigned int flags,
|
|
unsigned int opt_flags)
|
|
{
|
|
char *p = buffer;
|
|
int i, len = 0;
|
|
|
|
if (flags == MS_ALL_FLAGS) {
|
|
/* all flags are optional */
|
|
len = snprintf(p, size, "%s", default_match_pattern);
|
|
if (len < 0 || len >= size)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
for (i = 0; i <= 31; ++i) {
|
|
if ((opt_flags) & (1 << i))
|
|
len = snprintf(p, size, "(\\x%02x|)", i + 1);
|
|
else if (flags & (1 << i))
|
|
len = snprintf(p, size, "\\x%02x", i + 1);
|
|
else /* no entry = not set */
|
|
continue;
|
|
|
|
if (len < 0 || len >= size)
|
|
return FALSE;
|
|
p += len;
|
|
size -= len;
|
|
}
|
|
|
|
/* this needs to go once the backend is updated. */
|
|
if (buffer == p) {
|
|
/* match nothing - use impossible 254 as regex parser doesn't
|
|
* like the empty string
|
|
*/
|
|
if (size < 9)
|
|
return FALSE;
|
|
|
|
strcpy(p, "(\\xfe|)");
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int build_mnt_opts(std::string& buffer, struct value_list *opts)
|
|
{
|
|
struct value_list *ent;
|
|
pattern_t ptype;
|
|
int pos;
|
|
|
|
if (!opts) {
|
|
buffer.append(default_match_pattern);
|
|
return TRUE;
|
|
}
|
|
|
|
list_for_each(opts, ent) {
|
|
ptype = convert_aaregex_to_pcre(ent->value, 0, glob_default, buffer, &pos);
|
|
if (ptype == ePatternInvalid)
|
|
return FALSE;
|
|
|
|
if (ent->next)
|
|
buffer.append(",");
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void mnt_rule::warn_once(const char *name)
|
|
{
|
|
rule_t::warn_once(name, "mount rules not enforce");
|
|
}
|
|
|
|
|
|
int mnt_rule::gen_policy_remount(Profile &prof, int &count,
|
|
unsigned int flags, unsigned int opt_flags)
|
|
{
|
|
std::string mntbuf;
|
|
std::string devbuf;
|
|
std::string typebuf;
|
|
char flagsbuf[PATH_MAX + 3];
|
|
std::string optsbuf;
|
|
char class_mount_hdr[64];
|
|
const char *vec[5];
|
|
|
|
sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT);
|
|
|
|
/* remount can't be conditional on device and type */
|
|
/* rule class single byte header */
|
|
mntbuf.assign(class_mount_hdr);
|
|
if (mnt_point) {
|
|
/* both device && mnt_point or just mnt_point */
|
|
if (!convert_entry(mntbuf, mnt_point))
|
|
goto fail;
|
|
vec[0] = mntbuf.c_str();
|
|
} else {
|
|
if (!convert_entry(mntbuf, device))
|
|
goto fail;
|
|
vec[0] = mntbuf.c_str();
|
|
}
|
|
/* skip device */
|
|
vec[1] = default_match_pattern;
|
|
/* skip type */
|
|
vec[2] = default_match_pattern;
|
|
|
|
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_REMOUNT_FLAGS,
|
|
opt_flags & MS_REMOUNT_FLAGS))
|
|
goto fail;
|
|
|
|
vec[3] = flagsbuf;
|
|
|
|
perms_t tmpperms, tmpaudit;
|
|
if (opts) {
|
|
tmpperms = AA_MATCH_CONT;
|
|
tmpaudit = 0;
|
|
} else {
|
|
/* dependent on full expansion of any data match perms */
|
|
tmpperms = perms;
|
|
tmpaudit = audit == AUDIT_FORCE ? perms : 0;
|
|
}
|
|
/* match for up to but not including data
|
|
* if a data match is required this only has AA_MATCH_CONT perms
|
|
* else it has full perms
|
|
*/
|
|
if (!prof.policy.rules->add_rule_vec(rule_mode == RULE_DENY, tmpperms, tmpaudit, 4,
|
|
vec, dfaflags, false))
|
|
goto fail;
|
|
count++;
|
|
|
|
if (opts) {
|
|
/* rule with data match required */
|
|
optsbuf.clear();
|
|
if (!build_mnt_opts(optsbuf, opts))
|
|
goto fail;
|
|
vec[4] = optsbuf.c_str();
|
|
if (!prof.policy.rules->add_rule_vec(rule_mode == RULE_DENY, perms,
|
|
(audit == AUDIT_FORCE ? perms : 0),
|
|
5, vec, dfaflags, false))
|
|
goto fail;
|
|
count++;
|
|
}
|
|
|
|
return RULE_OK;
|
|
|
|
fail:
|
|
return RULE_ERROR;
|
|
}
|
|
|
|
int mnt_rule::gen_policy_bind_mount(Profile &prof, int &count,
|
|
unsigned int flags, unsigned int opt_flags)
|
|
{
|
|
std::string mntbuf;
|
|
std::string devbuf;
|
|
std::string typebuf;
|
|
char flagsbuf[PATH_MAX + 3];
|
|
std::string optsbuf;
|
|
char class_mount_hdr[64];
|
|
const char *vec[5];
|
|
|
|
sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT);
|
|
|
|
/* bind mount rules can't be conditional on dev_type or data */
|
|
/* rule class single byte header */
|
|
mntbuf.assign(class_mount_hdr);
|
|
if (!convert_entry(mntbuf, mnt_point))
|
|
goto fail;
|
|
vec[0] = mntbuf.c_str();
|
|
if (!clear_and_convert_entry(devbuf, device))
|
|
goto fail;
|
|
vec[1] = devbuf.c_str();
|
|
/* skip type */
|
|
vec[2] = default_match_pattern;
|
|
|
|
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_BIND_FLAGS,
|
|
opt_flags & MS_BIND_FLAGS))
|
|
goto fail;
|
|
vec[3] = flagsbuf;
|
|
if (!prof.policy.rules->add_rule_vec(rule_mode == RULE_DENY, perms, audit == AUDIT_FORCE ? perms : 0,
|
|
4, vec,
|
|
dfaflags, false))
|
|
goto fail;
|
|
count++;
|
|
|
|
return RULE_OK;
|
|
|
|
fail:
|
|
return RULE_ERROR;
|
|
}
|
|
|
|
int mnt_rule::gen_policy_change_mount_type(Profile &prof, int &count,
|
|
unsigned int flags,
|
|
unsigned int opt_flags)
|
|
{
|
|
std::string mntbuf;
|
|
std::string devbuf;
|
|
std::string typebuf;
|
|
char flagsbuf[PATH_MAX + 3];
|
|
std::string optsbuf;
|
|
char class_mount_hdr[64];
|
|
const char *vec[5];
|
|
char *mountpoint = mnt_point;
|
|
|
|
sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT);
|
|
|
|
/* change type base rules can specify the mount point by using
|
|
* the parser token position reserved to device. that's why if
|
|
* the mount point is not specified, we use device in its
|
|
* place. this is a deprecated behavior.
|
|
*
|
|
* change type base rules can not be conditional on device
|
|
* (source), device type or data
|
|
*/
|
|
/* rule class single byte header */
|
|
mntbuf.assign(class_mount_hdr);
|
|
if (flags && flags != MS_ALL_FLAGS && device && mnt_point) {
|
|
PERROR("source and mount point cannot be used at the "
|
|
"same time for propagation type flags");
|
|
goto fail;
|
|
} else if (device && !mnt_point) {
|
|
pwarn(WARN_DEPRECATED, _("The use of source as mount point for "
|
|
"propagation type flags is deprecated.\n"));
|
|
mountpoint = device;
|
|
}
|
|
if (!convert_entry(mntbuf, mountpoint))
|
|
goto fail;
|
|
vec[0] = mntbuf.c_str();
|
|
/* skip device and type */
|
|
vec[1] = default_match_pattern;
|
|
vec[2] = default_match_pattern;
|
|
|
|
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_MAKE_FLAGS,
|
|
opt_flags & MS_MAKE_FLAGS))
|
|
goto fail;
|
|
vec[3] = flagsbuf;
|
|
if (!prof.policy.rules->add_rule_vec(rule_mode == RULE_DENY, perms, audit == AUDIT_FORCE ? perms : 0,
|
|
4, vec,
|
|
dfaflags, false))
|
|
goto fail;
|
|
count++;
|
|
|
|
return RULE_OK;
|
|
|
|
fail:
|
|
return RULE_ERROR;
|
|
}
|
|
|
|
int mnt_rule::gen_policy_move_mount(Profile &prof, int &count,
|
|
unsigned int flags, unsigned int opt_flags)
|
|
{
|
|
std::string mntbuf;
|
|
std::string devbuf;
|
|
std::string typebuf;
|
|
char flagsbuf[PATH_MAX + 3];
|
|
std::string optsbuf;
|
|
char class_mount_hdr[64];
|
|
const char *vec[5];
|
|
|
|
sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT);
|
|
|
|
/* mount move rules can not be conditional on dev_type,
|
|
* or data
|
|
*/
|
|
/* rule class single byte header */
|
|
mntbuf.assign(class_mount_hdr);
|
|
if (!convert_entry(mntbuf, mnt_point))
|
|
goto fail;
|
|
vec[0] = mntbuf.c_str();
|
|
if (!clear_and_convert_entry(devbuf, device))
|
|
goto fail;
|
|
vec[1] = devbuf.c_str();
|
|
/* skip type */
|
|
vec[2] = default_match_pattern;
|
|
|
|
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_MOVE_FLAGS,
|
|
opt_flags & MS_MOVE_FLAGS))
|
|
goto fail;
|
|
vec[3] = flagsbuf;
|
|
if (!prof.policy.rules->add_rule_vec(rule_mode == RULE_DENY, perms, audit == AUDIT_FORCE ? perms : 0,
|
|
4, vec,
|
|
dfaflags, false))
|
|
goto fail;
|
|
count++;
|
|
|
|
return RULE_OK;
|
|
|
|
fail:
|
|
return RULE_ERROR;
|
|
}
|
|
|
|
int mnt_rule::gen_policy_new_mount(Profile &prof, int &count,
|
|
unsigned int flags, unsigned int opt_flags)
|
|
{
|
|
std::string mntbuf;
|
|
std::string devbuf;
|
|
std::string typebuf;
|
|
char flagsbuf[PATH_MAX + 3];
|
|
std::string optsbuf;
|
|
char class_mount_hdr[64];
|
|
const char *vec[5];
|
|
|
|
sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT);
|
|
|
|
/* rule class single byte header */
|
|
mntbuf.assign(class_mount_hdr);
|
|
if (!convert_entry(mntbuf, mnt_point))
|
|
goto fail;
|
|
vec[0] = mntbuf.c_str();
|
|
if (!clear_and_convert_entry(devbuf, device))
|
|
goto fail;
|
|
vec[1] = devbuf.c_str();
|
|
typebuf.clear();
|
|
if (!build_list_val_expr(typebuf, dev_type))
|
|
goto fail;
|
|
vec[2] = typebuf.c_str();
|
|
|
|
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_NEW_FLAGS,
|
|
opt_flags & MS_NEW_FLAGS))
|
|
goto fail;
|
|
vec[3] = flagsbuf;
|
|
|
|
perms_t tmpperms, tmpaudit;
|
|
if (opts) {
|
|
tmpperms = AA_MATCH_CONT;
|
|
tmpaudit = 0;
|
|
} else {
|
|
tmpperms = perms;
|
|
tmpaudit = audit == AUDIT_FORCE ? perms : 0;
|
|
}
|
|
/* rule for match without required data || data MATCH_CONT */
|
|
if (!prof.policy.rules->add_rule_vec(rule_mode == RULE_DENY, tmpperms, tmpaudit, 4,
|
|
vec, dfaflags, false))
|
|
goto fail;
|
|
count++;
|
|
|
|
if (opts) {
|
|
/* rule with data match required */
|
|
optsbuf.clear();
|
|
if (!build_mnt_opts(optsbuf, opts))
|
|
goto fail;
|
|
vec[4] = optsbuf.c_str();
|
|
if (!prof.policy.rules->add_rule_vec(rule_mode == RULE_DENY, perms,
|
|
audit == AUDIT_FORCE ? perms : 0,
|
|
5, vec, dfaflags, false))
|
|
goto fail;
|
|
count++;
|
|
}
|
|
|
|
return RULE_OK;
|
|
|
|
fail:
|
|
return RULE_ERROR;
|
|
}
|
|
|
|
int mnt_rule::gen_flag_rules(Profile &prof, int &count, unsigned int flags,
|
|
unsigned int opt_flags)
|
|
{
|
|
/*
|
|
* XXX: added !flags to cover cases like:
|
|
* mount options in (bind) /d -> /4,
|
|
*/
|
|
if ((perms & AA_MAY_MOUNT) && (!flags || flags == MS_ALL_FLAGS)) {
|
|
/* no mount flags specified, generate multiple rules */
|
|
if (!device && !dev_type &&
|
|
gen_policy_remount(prof, count, flags, opt_flags) == RULE_ERROR)
|
|
return RULE_ERROR;
|
|
if (!dev_type && !opts &&
|
|
gen_policy_bind_mount(prof, count, flags, opt_flags) == RULE_ERROR)
|
|
return RULE_ERROR;
|
|
if (!dev_type && !opts &&
|
|
gen_policy_change_mount_type(prof, count, flags, opt_flags) == RULE_ERROR)
|
|
return RULE_ERROR;
|
|
if (!dev_type && !opts &&
|
|
gen_policy_move_mount(prof, count, flags, opt_flags) == RULE_ERROR)
|
|
return RULE_ERROR;
|
|
|
|
return gen_policy_new_mount(prof, count, flags, opt_flags);
|
|
} else if ((perms & AA_MAY_MOUNT) && (flags & MS_REMOUNT)
|
|
&& !device && !dev_type) {
|
|
return gen_policy_remount(prof, count, flags, opt_flags);
|
|
} else if ((perms & AA_MAY_MOUNT) && (flags & MS_BIND)
|
|
&& !dev_type && !opts) {
|
|
return gen_policy_bind_mount(prof, count, flags, opt_flags);
|
|
} else if ((perms & AA_MAY_MOUNT) &&
|
|
(flags & (MS_MAKE_CMDS))
|
|
&& !dev_type && !opts) {
|
|
return gen_policy_change_mount_type(prof, count, flags, opt_flags);
|
|
} else if ((perms & AA_MAY_MOUNT) && (flags & MS_MOVE)
|
|
&& !dev_type && !opts) {
|
|
return gen_policy_move_mount(prof, count, flags, opt_flags);
|
|
} else if ((perms & AA_MAY_MOUNT) &&
|
|
((flags | opt_flags) & ~MS_CMDS)) {
|
|
/* generic mount if flags are set that are not covered by
|
|
* above commands
|
|
*/
|
|
return gen_policy_new_mount(prof, count, flags, opt_flags);
|
|
} /* else must be RULE_OK for some rules */
|
|
|
|
return RULE_OK;
|
|
}
|
|
|
|
int mnt_rule::gen_policy_re(Profile &prof)
|
|
{
|
|
std::string mntbuf;
|
|
std::string devbuf;
|
|
std::string typebuf;
|
|
std::string optsbuf;
|
|
char class_mount_hdr[64];
|
|
const char *vec[5];
|
|
int count = 0;
|
|
|
|
if (!features_supports_mount) {
|
|
warn_once(prof.name);
|
|
return RULE_NOT_SUPPORTED;
|
|
}
|
|
|
|
sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT);
|
|
|
|
/* a single mount rule may result in multiple matching rules being
|
|
* created in the backend to cover all the possible choices
|
|
*/
|
|
for (size_t i = 0; i < flagsv.size(); i++) {
|
|
for (size_t j = 0; j < opt_flagsv.size(); j++) {
|
|
if (gen_flag_rules(prof, count, flagsv[i], opt_flagsv[j]) == RULE_ERROR)
|
|
goto fail;
|
|
}
|
|
}
|
|
if (perms & AA_MAY_UMOUNT) {
|
|
/* rule class single byte header */
|
|
mntbuf.assign(class_mount_hdr);
|
|
if (!convert_entry(mntbuf, mnt_point))
|
|
goto fail;
|
|
vec[0] = mntbuf.c_str();
|
|
if (!prof.policy.rules->add_rule_vec(rule_mode == RULE_DENY, perms,
|
|
(audit == AUDIT_FORCE ? perms : 0), 1, vec,
|
|
dfaflags, false))
|
|
goto fail;
|
|
count++;
|
|
}
|
|
if (perms & AA_MAY_PIVOTROOT) {
|
|
/* rule class single byte header */
|
|
mntbuf.assign(class_mount_hdr);
|
|
if (!convert_entry(mntbuf, mnt_point))
|
|
goto fail;
|
|
vec[0] = mntbuf.c_str();
|
|
if (!clear_and_convert_entry(devbuf, device))
|
|
goto fail;
|
|
vec[1] = devbuf.c_str();
|
|
if (!prof.policy.rules->add_rule_vec(rule_mode == RULE_DENY, perms,
|
|
(audit == AUDIT_FORCE ? perms : 0), 2, vec,
|
|
dfaflags, false))
|
|
goto fail;
|
|
count++;
|
|
}
|
|
|
|
if (!count)
|
|
/* didn't actually encode anything */
|
|
goto fail;
|
|
|
|
return RULE_OK;
|
|
|
|
fail:
|
|
PERROR("Encoding of mount rule failed\n");
|
|
return RULE_ERROR;
|
|
}
|
|
|
|
void mnt_rule::post_parse_profile(Profile &prof)
|
|
{
|
|
if (trans) {
|
|
perms_t perms = 0;
|
|
int n = add_entry_to_x_table(&prof, trans);
|
|
if (!n) {
|
|
PERROR("Profile %s has too many specified profile transitions.\n", prof.name);
|
|
exit(1);
|
|
}
|
|
|
|
if (perms & AA_USER_EXEC)
|
|
perms |= SHIFT_PERMS(n << 10, AA_USER_SHIFT);
|
|
if (perms & AA_OTHER_EXEC)
|
|
perms |= SHIFT_PERMS(n << 10, AA_OTHER_SHIFT);
|
|
perms = ((perms & ~AA_ALL_EXEC_MODIFIERS) |
|
|
(perms & AA_ALL_EXEC_MODIFIERS));
|
|
|
|
trans = NULL;
|
|
}
|
|
}
|
|
|
|
|