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

When is_blacklisted() was internal to the parser, it would print an error message when encountering some file names. If the path parameter was non-null, the error message would include the file path instead of the file name. Now that the function has been moved to libapparmor, callers are expected to print the appropriate error message if _aa_is_blacklisted() returns -1. Since the error message printing no longer occurs inside of _aa_is_blacklisted(), the path parameter can be removed. Signed-off-by: Tyler Hicks <tyhicks@canonical.com> Acked-by: John Johansen <john.johansen@canonical.com>
1107 lines
26 KiB
C
1107 lines
26 KiB
C
/*
|
|
* Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
|
|
* NOVELL (All rights reserved)
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/* assistance routines */
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <linux/capability.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/apparmor.h>
|
|
#include <sys/apparmor_private.h>
|
|
|
|
#include "lib.h"
|
|
#include "parser.h"
|
|
#include "profile.h"
|
|
#include "parser_yacc.h"
|
|
#include "mount.h"
|
|
#include "dbus.h"
|
|
|
|
/* #define DEBUG */
|
|
#ifdef DEBUG
|
|
#undef PDEBUG
|
|
#define PDEBUG(fmt, args...) fprintf(stderr, "Lexer: " fmt, ## args)
|
|
#else
|
|
#undef PDEBUG
|
|
#define PDEBUG(fmt, args...) /* Do nothing */
|
|
#endif
|
|
#define NPDEBUG(fmt, args...) /* Do nothing */
|
|
|
|
int is_blacklisted(const char *name, const char *path)
|
|
{
|
|
int retval = _aa_is_blacklisted(name);
|
|
|
|
if (retval == -1)
|
|
PERROR("Ignoring: '%s'\n", path ? path : name);
|
|
|
|
return !retval ? 0 : 1;
|
|
}
|
|
|
|
struct keyword_table {
|
|
const char *keyword;
|
|
int token;
|
|
};
|
|
|
|
static struct keyword_table keyword_table[] = {
|
|
/* network */
|
|
{"network", TOK_NETWORK},
|
|
{"unix", TOK_UNIX},
|
|
/* misc keywords */
|
|
{"capability", TOK_CAPABILITY},
|
|
{"if", TOK_IF},
|
|
{"else", TOK_ELSE},
|
|
{"not", TOK_NOT},
|
|
{"defined", TOK_DEFINED},
|
|
{"change_profile", TOK_CHANGE_PROFILE},
|
|
{"unsafe", TOK_UNSAFE},
|
|
{"safe", TOK_SAFE},
|
|
{"link", TOK_LINK},
|
|
{"owner", TOK_OWNER},
|
|
{"user", TOK_OWNER},
|
|
{"other", TOK_OTHER},
|
|
{"subset", TOK_SUBSET},
|
|
{"audit", TOK_AUDIT},
|
|
{"deny", TOK_DENY},
|
|
{"allow", TOK_ALLOW},
|
|
{"set", TOK_SET},
|
|
{"rlimit", TOK_RLIMIT},
|
|
{"alias", TOK_ALIAS},
|
|
{"rewrite", TOK_ALIAS},
|
|
{"ptrace", TOK_PTRACE},
|
|
{"file", TOK_FILE},
|
|
{"mount", TOK_MOUNT},
|
|
{"remount", TOK_REMOUNT},
|
|
{"umount", TOK_UMOUNT},
|
|
{"unmount", TOK_UMOUNT},
|
|
{"pivot_root", TOK_PIVOTROOT},
|
|
{"in", TOK_IN},
|
|
{"dbus", TOK_DBUS},
|
|
{"signal", TOK_SIGNAL},
|
|
{"send", TOK_SEND},
|
|
{"receive", TOK_RECEIVE},
|
|
{"bind", TOK_BIND},
|
|
{"read", TOK_READ},
|
|
{"write", TOK_WRITE},
|
|
{"eavesdrop", TOK_EAVESDROP},
|
|
{"peer", TOK_PEER},
|
|
{"trace", TOK_TRACE},
|
|
{"tracedby", TOK_TRACEDBY},
|
|
{"readby", TOK_READBY},
|
|
|
|
/* terminate */
|
|
{NULL, 0}
|
|
};
|
|
|
|
static struct keyword_table rlimit_table[] = {
|
|
{"cpu", RLIMIT_CPU},
|
|
{"fsize", RLIMIT_FSIZE},
|
|
{"data", RLIMIT_DATA},
|
|
{"stack", RLIMIT_STACK},
|
|
{"core", RLIMIT_CORE},
|
|
{"rss", RLIMIT_RSS},
|
|
{"nofile", RLIMIT_NOFILE},
|
|
{"ofile", RLIMIT_OFILE},
|
|
{"as", RLIMIT_AS},
|
|
{"nproc", RLIMIT_NPROC},
|
|
{"memlock", RLIMIT_MEMLOCK},
|
|
{"locks", RLIMIT_LOCKS},
|
|
{"sigpending", RLIMIT_SIGPENDING},
|
|
{"msgqueue", RLIMIT_MSGQUEUE},
|
|
#ifdef RLIMIT_NICE
|
|
{"nice", RLIMIT_NICE},
|
|
#endif
|
|
#ifdef RLIMIT_RTPRIO
|
|
{"rtprio", RLIMIT_RTPRIO},
|
|
#endif
|
|
#ifdef RLIMIT_RTTIME
|
|
{"rttime", RLIMIT_RTTIME},
|
|
#endif
|
|
/* terminate */
|
|
{NULL, 0}
|
|
};
|
|
|
|
/* for alpha matches, check for keywords */
|
|
static int get_table_token(const char *name unused, struct keyword_table *table,
|
|
const char *keyword)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; table[i].keyword; i++) {
|
|
PDEBUG("Checking %s %s\n", name, table[i].keyword);
|
|
if (strcmp(keyword, table[i].keyword) == 0) {
|
|
PDEBUG("Found %s %s\n", name, table[i].keyword);
|
|
return table[i].token;
|
|
}
|
|
}
|
|
|
|
PDEBUG("Unable to find %s %s\n", name, keyword);
|
|
return -1;
|
|
}
|
|
|
|
static struct keyword_table capability_table[] = {
|
|
/* capabilities */
|
|
#include "cap_names.h"
|
|
#ifndef CAP_SYSLOG
|
|
{"syslog", 34},
|
|
#endif
|
|
/* terminate */
|
|
{NULL, 0}
|
|
};
|
|
|
|
/* for alpha matches, check for keywords */
|
|
int get_keyword_token(const char *keyword)
|
|
{
|
|
return get_table_token("keyword", keyword_table, keyword);
|
|
}
|
|
|
|
int name_to_capability(const char *keyword)
|
|
{
|
|
return get_table_token("capability", capability_table, keyword);
|
|
}
|
|
|
|
int get_rlimit(const char *name)
|
|
{
|
|
return get_table_token("rlimit", rlimit_table, name);
|
|
}
|
|
|
|
char *processunquoted(const char *string, int len)
|
|
{
|
|
char *buffer, *s;
|
|
|
|
s = buffer = (char *) malloc(len + 1);
|
|
if (!buffer)
|
|
return NULL;
|
|
|
|
while (len > 0) {
|
|
const char *pos = string + 1;
|
|
long c;
|
|
if (*string == '\\' && len > 1 &&
|
|
(c = strn_escseq(&pos, "", len)) != -1) {
|
|
/* catch \\ or \134 and other aare special chars and
|
|
* pass it through to be handled by the backend
|
|
* pcre conversion
|
|
*/
|
|
if (c == 0) {
|
|
strncpy(s, string, pos - string);
|
|
s += pos - string;
|
|
} else if (strchr("*?[]{}^,\\", c) != NULL) {
|
|
*s++ = '\\';
|
|
*s++ = c;
|
|
} else
|
|
*s++ = c;
|
|
len -= pos - string;
|
|
string = pos;
|
|
} else {
|
|
/* either unescaped char OR
|
|
* unsupported escape sequence resulting in char being
|
|
* copied.
|
|
*/
|
|
*s++ = *string++;
|
|
len--;
|
|
}
|
|
}
|
|
*s = 0;
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/* rewrite a quoted string substituting escaped characters for the
|
|
* real thing. Strip the quotes around the string */
|
|
char *processquoted(const char *string, int len)
|
|
{
|
|
/* skip leading " and eat trailing " */
|
|
if (*string == '"') {
|
|
len -= 2;
|
|
if (len < 0) /* start and end point to same quote */
|
|
len = 0;
|
|
return processunquoted(string + 1, len);
|
|
}
|
|
|
|
/* no quotes? treat as unquoted */
|
|
return processunquoted(string, len);
|
|
}
|
|
|
|
char *processid(const char *string, int len)
|
|
{
|
|
/* lexer should never call this fn if len <= 0 */
|
|
assert(len > 0);
|
|
|
|
if (*string == '"')
|
|
return processquoted(string, len);
|
|
return processunquoted(string, len);
|
|
}
|
|
|
|
/* strip off surrounding delimiters around variables */
|
|
char *process_var(const char *var)
|
|
{
|
|
const char *orig = var;
|
|
int len = strlen(var);
|
|
|
|
if (*orig == '@' || *orig == '$') {
|
|
orig++;
|
|
len--;
|
|
} else {
|
|
PERROR("ASSERT: Found var '%s' without variable prefix\n",
|
|
var);
|
|
return NULL;
|
|
}
|
|
|
|
if (*orig == '{') {
|
|
orig++;
|
|
len--;
|
|
if (orig[len - 1] != '}') {
|
|
PERROR("ASSERT: No matching '}' in variable '%s'\n",
|
|
var);
|
|
return NULL;
|
|
} else
|
|
len--;
|
|
}
|
|
|
|
return strndup(orig, len);
|
|
}
|
|
|
|
/* returns -1 if value != true or false, otherwise 0 == false, 1 == true */
|
|
int str_to_boolean(const char *value)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (strcasecmp("TRUE", value) == 0)
|
|
retval = 1;
|
|
if (strcasecmp("FALSE", value) == 0)
|
|
retval = 0;
|
|
return retval;
|
|
}
|
|
|
|
static int warned_uppercase = 0;
|
|
|
|
void warn_uppercase(void)
|
|
{
|
|
if (!warned_uppercase) {
|
|
pwarn(_("Uppercase qualifiers \"RWLIMX\" are deprecated, please convert to lowercase\n"
|
|
"See the apparmor.d(5) manpage for details.\n"));
|
|
warned_uppercase = 1;
|
|
}
|
|
}
|
|
|
|
static int parse_sub_mode(const char *str_mode, const char *mode_desc unused)
|
|
{
|
|
|
|
#define IS_DIFF_QUAL(mode, q) (((mode) & AA_MAY_EXEC) && (((mode) & AA_EXEC_TYPE) != ((q) & AA_EXEC_TYPE)))
|
|
|
|
int mode = 0;
|
|
const char *p;
|
|
|
|
PDEBUG("Parsing mode: %s\n", str_mode);
|
|
|
|
if (!str_mode)
|
|
return 0;
|
|
|
|
p = str_mode;
|
|
while (*p) {
|
|
char thisc = *p;
|
|
char next = *(p + 1);
|
|
char lower;
|
|
int tmode = 0;
|
|
|
|
reeval:
|
|
switch (thisc) {
|
|
case COD_READ_CHAR:
|
|
if (read_implies_exec) {
|
|
PDEBUG("Parsing mode: found %s READ imply X\n", mode_desc);
|
|
mode |= AA_MAY_READ | AA_OLD_EXEC_MMAP;
|
|
} else {
|
|
PDEBUG("Parsing mode: found %s READ\n", mode_desc);
|
|
mode |= AA_MAY_READ;
|
|
}
|
|
break;
|
|
|
|
case COD_WRITE_CHAR:
|
|
PDEBUG("Parsing mode: found %s WRITE\n", mode_desc);
|
|
if ((mode & AA_MAY_APPEND) && !(mode & AA_MAY_WRITE))
|
|
yyerror(_("Conflict 'a' and 'w' perms are mutually exclusive."));
|
|
mode |= AA_MAY_WRITE | AA_MAY_APPEND;
|
|
break;
|
|
|
|
case COD_APPEND_CHAR:
|
|
PDEBUG("Parsing mode: found %s APPEND\n", mode_desc);
|
|
if (mode & AA_MAY_WRITE)
|
|
yyerror(_("Conflict 'a' and 'w' perms are mutually exclusive."));
|
|
mode |= AA_MAY_APPEND;
|
|
break;
|
|
|
|
case COD_LINK_CHAR:
|
|
PDEBUG("Parsing mode: found %s LINK\n", mode_desc);
|
|
mode |= AA_OLD_MAY_LINK;
|
|
break;
|
|
|
|
case COD_LOCK_CHAR:
|
|
PDEBUG("Parsing mode: found %s LOCK\n", mode_desc);
|
|
mode |= AA_OLD_MAY_LOCK;
|
|
break;
|
|
|
|
case COD_INHERIT_CHAR:
|
|
PDEBUG("Parsing mode: found INHERIT\n");
|
|
if (mode & AA_EXEC_MODIFIERS) {
|
|
yyerror(_("Exec qualifier 'i' invalid, conflicting qualifier already specified"));
|
|
} else {
|
|
if (next != tolower(next))
|
|
warn_uppercase();
|
|
mode |= (AA_EXEC_INHERIT | AA_MAY_EXEC);
|
|
p++; /* skip 'x' */
|
|
}
|
|
break;
|
|
|
|
case COD_UNSAFE_UNCONFINED_CHAR:
|
|
tmode = AA_EXEC_UNSAFE;
|
|
pwarn(_("Unconfined exec qualifier (%c%c) allows some dangerous environment variables "
|
|
"to be passed to the unconfined process; 'man 5 apparmor.d' for details.\n"),
|
|
COD_UNSAFE_UNCONFINED_CHAR, COD_EXEC_CHAR);
|
|
/* fall through */
|
|
case COD_UNCONFINED_CHAR:
|
|
tmode |= AA_EXEC_UNCONFINED | AA_MAY_EXEC;
|
|
PDEBUG("Parsing mode: found UNCONFINED\n");
|
|
if (IS_DIFF_QUAL(mode, tmode)) {
|
|
yyerror(_("Exec qualifier '%c' invalid, conflicting qualifier already specified"),
|
|
thisc);
|
|
} else {
|
|
if (next != tolower(next))
|
|
warn_uppercase();
|
|
mode |= tmode;
|
|
p++; /* skip 'x' */
|
|
}
|
|
tmode = 0;
|
|
break;
|
|
|
|
case COD_UNSAFE_PROFILE_CHAR:
|
|
case COD_UNSAFE_LOCAL_CHAR:
|
|
tmode = AA_EXEC_UNSAFE;
|
|
/* fall through */
|
|
case COD_PROFILE_CHAR:
|
|
case COD_LOCAL_CHAR:
|
|
if (tolower(thisc) == COD_UNSAFE_PROFILE_CHAR)
|
|
tmode |= AA_EXEC_PROFILE | AA_MAY_EXEC;
|
|
else
|
|
{
|
|
tmode |= AA_EXEC_LOCAL | AA_MAY_EXEC;
|
|
}
|
|
PDEBUG("Parsing mode: found PROFILE\n");
|
|
if (tolower(next) == COD_INHERIT_CHAR) {
|
|
tmode |= AA_EXEC_INHERIT;
|
|
if (IS_DIFF_QUAL(mode, tmode)) {
|
|
yyerror(_("Exec qualifier '%c%c' invalid, conflicting qualifier already specified"), thisc, next);
|
|
} else {
|
|
mode |= tmode;
|
|
p += 2; /* skip x */
|
|
}
|
|
} else if (tolower(next) == COD_UNSAFE_UNCONFINED_CHAR) {
|
|
tmode |= AA_EXEC_PUX;
|
|
if (IS_DIFF_QUAL(mode, tmode)) {
|
|
yyerror(_("Exec qualifier '%c%c' invalid, conflicting qualifier already specified"), thisc, next);
|
|
} else {
|
|
mode |= tmode;
|
|
p += 2; /* skip x */
|
|
}
|
|
} else if (IS_DIFF_QUAL(mode, tmode)) {
|
|
yyerror(_("Exec qualifier '%c' invalid, conflicting qualifier already specified"), thisc);
|
|
|
|
} else {
|
|
if (next != tolower(next))
|
|
warn_uppercase();
|
|
mode |= tmode;
|
|
p++; /* skip 'x' */
|
|
}
|
|
tmode = 0;
|
|
break;
|
|
|
|
case COD_MMAP_CHAR:
|
|
PDEBUG("Parsing mode: found %s MMAP\n", mode_desc);
|
|
mode |= AA_OLD_EXEC_MMAP;
|
|
break;
|
|
|
|
case COD_EXEC_CHAR:
|
|
/* thisc is valid for deny rules, and named transitions
|
|
* but invalid for regular x transitions
|
|
* sort it out later.
|
|
*/
|
|
mode |= AA_MAY_EXEC;
|
|
break;
|
|
|
|
/* error cases */
|
|
|
|
default:
|
|
lower = tolower(thisc);
|
|
switch (lower) {
|
|
case COD_READ_CHAR:
|
|
case COD_WRITE_CHAR:
|
|
case COD_APPEND_CHAR:
|
|
case COD_LINK_CHAR:
|
|
case COD_INHERIT_CHAR:
|
|
case COD_MMAP_CHAR:
|
|
case COD_EXEC_CHAR:
|
|
PDEBUG("Parsing mode: found invalid upper case char %c\n", thisc);
|
|
warn_uppercase();
|
|
thisc = lower;
|
|
goto reeval;
|
|
break;
|
|
default:
|
|
yyerror(_("Internal: unexpected mode character '%c' in input"),
|
|
thisc);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
p++;
|
|
}
|
|
|
|
PDEBUG("Parsed mode: %s 0x%x\n", str_mode, mode);
|
|
|
|
return mode;
|
|
}
|
|
|
|
int parse_mode(const char *str_mode)
|
|
{
|
|
int tmp, mode = 0;
|
|
tmp = parse_sub_mode(str_mode, "");
|
|
mode = SHIFT_MODE(tmp, AA_USER_SHIFT);
|
|
mode |= SHIFT_MODE(tmp, AA_OTHER_SHIFT);
|
|
if (mode & ~AA_VALID_PERMS)
|
|
yyerror(_("Internal error generated invalid perm 0x%llx\n"), mode);
|
|
return mode;
|
|
}
|
|
|
|
static int parse_X_sub_mode(const char *X, const char *str_mode, int *result, int fail, const char *mode_desc unused)
|
|
{
|
|
int mode = 0;
|
|
const char *p;
|
|
|
|
PDEBUG("Parsing %s mode: %s\n", X, 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 %s mode: found %s READ\n", X, mode_desc);
|
|
mode |= AA_DBUS_RECEIVE;
|
|
break;
|
|
|
|
case COD_WRITE_CHAR:
|
|
PDEBUG("Parsing %s mode: found %s WRITE\n", X,
|
|
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 %s mode: found invalid upper case char %c\n",
|
|
X, current);
|
|
warn_uppercase();
|
|
current = lower;
|
|
goto reeval;
|
|
break;
|
|
default:
|
|
if (fail)
|
|
yyerror(_("Internal: unexpected %s mode character '%c' in input"),
|
|
X, current);
|
|
else
|
|
return 0;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
p++;
|
|
}
|
|
|
|
PDEBUG("Parsed %s mode: %s 0x%x\n", X, str_mode, mode);
|
|
|
|
*result = mode;
|
|
return 1;
|
|
}
|
|
|
|
int parse_X_mode(const char *X, int valid, const char *str_mode, int *mode, int fail)
|
|
{
|
|
*mode = 0;
|
|
if (!parse_X_sub_mode(X, str_mode, mode, fail, ""))
|
|
return 0;
|
|
if (*mode & ~valid) {
|
|
if (fail)
|
|
yyerror(_("Internal error generated invalid %s perm 0x%x\n"),
|
|
X, mode);
|
|
else
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
struct cod_entry *new_entry(char *ns, char *id, int mode, char *link_id)
|
|
{
|
|
struct cod_entry *entry = NULL;
|
|
|
|
entry = (struct cod_entry *)calloc(1, sizeof(struct cod_entry));
|
|
if (!entry)
|
|
return NULL;
|
|
|
|
entry->ns = ns;
|
|
entry->name = id;
|
|
entry->link_name = link_id;
|
|
entry->mode = mode;
|
|
entry->audit = 0;
|
|
entry->deny = FALSE;
|
|
|
|
entry->pattern_type = ePatternInvalid;
|
|
entry->pat.regex = NULL;
|
|
|
|
entry->next = NULL;
|
|
|
|
PDEBUG(" Insertion of: (%s)\n", entry->name);
|
|
return entry;
|
|
}
|
|
|
|
struct cod_entry *copy_cod_entry(struct cod_entry *orig)
|
|
{
|
|
struct cod_entry *entry = NULL;
|
|
|
|
entry = (struct cod_entry *)calloc(1, sizeof(struct cod_entry));
|
|
if (!entry)
|
|
return NULL;
|
|
|
|
DUP_STRING(orig, entry, ns, err);
|
|
DUP_STRING(orig, entry, name, err);
|
|
DUP_STRING(orig, entry, link_name, err);
|
|
entry->mode = orig->mode;
|
|
entry->audit = orig->audit;
|
|
entry->deny = orig->deny;
|
|
|
|
/* XXX - need to create copies of the patterns, too */
|
|
entry->pattern_type = orig->pattern_type;
|
|
entry->pat.regex = NULL;
|
|
|
|
entry->next = orig->next;
|
|
|
|
return entry;
|
|
|
|
err:
|
|
free_cod_entries(entry);
|
|
return NULL;
|
|
}
|
|
|
|
void free_cod_entries(struct cod_entry *list)
|
|
{
|
|
if (!list)
|
|
return;
|
|
if (list->next)
|
|
free_cod_entries(list->next);
|
|
if (list->ns)
|
|
free(list->ns);
|
|
if (list->name)
|
|
free(list->name);
|
|
if (list->link_name)
|
|
free(list->link_name);
|
|
if (list->nt_name)
|
|
free(list->nt_name);
|
|
if (list->pat.regex)
|
|
free(list->pat.regex);
|
|
free(list);
|
|
}
|
|
|
|
static void debug_base_perm_mask(int mask)
|
|
{
|
|
if (HAS_MAY_READ(mask))
|
|
printf("%c", COD_READ_CHAR);
|
|
if (HAS_MAY_WRITE(mask))
|
|
printf("%c", COD_WRITE_CHAR);
|
|
if (HAS_MAY_APPEND(mask))
|
|
printf("%c", COD_APPEND_CHAR);
|
|
if (HAS_MAY_LINK(mask))
|
|
printf("%c", COD_LINK_CHAR);
|
|
if (HAS_MAY_LOCK(mask))
|
|
printf("%c", COD_LOCK_CHAR);
|
|
if (HAS_EXEC_MMAP(mask))
|
|
printf("%c", COD_MMAP_CHAR);
|
|
if (HAS_MAY_EXEC(mask))
|
|
printf("%c", COD_EXEC_CHAR);
|
|
}
|
|
|
|
void debug_cod_entries(struct cod_entry *list)
|
|
{
|
|
struct cod_entry *item = NULL;
|
|
|
|
printf("--- Entries ---\n");
|
|
|
|
list_for_each(list, item) {
|
|
if (!item)
|
|
printf("Item is NULL!\n");
|
|
|
|
printf("Mode:\t");
|
|
if (HAS_CHANGE_PROFILE(item->mode))
|
|
printf(" change_profile");
|
|
if (HAS_EXEC_UNSAFE(item->mode))
|
|
printf(" unsafe");
|
|
debug_base_perm_mask(SHIFT_TO_BASE(item->mode, AA_USER_SHIFT));
|
|
printf(":");
|
|
debug_base_perm_mask(SHIFT_TO_BASE(item->mode, AA_OTHER_SHIFT));
|
|
if (item->name)
|
|
printf("\tName:\t(%s)\n", item->name);
|
|
else
|
|
printf("\tName:\tNULL\n");
|
|
|
|
if (item->ns)
|
|
printf("\tNs:\t(%s)\n", item->ns);
|
|
|
|
if (AA_LINK_BITS & item->mode)
|
|
printf("\tlink:\t(%s)\n", item->link_name ? item->link_name : "/**");
|
|
|
|
}
|
|
}
|
|
|
|
|
|
static const char *capnames[] = {
|
|
"chown",
|
|
"dac_override",
|
|
"dac_read_search",
|
|
"fowner",
|
|
"fsetid",
|
|
"kill",
|
|
"setgid",
|
|
"setuid",
|
|
"setpcap",
|
|
"linux_immutable",
|
|
"net_bind_service",
|
|
"net_broadcast",
|
|
"net_admin",
|
|
"net_raw",
|
|
"ipc_lock",
|
|
"ipc_owner",
|
|
"sys_module",
|
|
"sys_rawio",
|
|
"sys_chroot",
|
|
"sys_ptrace",
|
|
"sys_pacct",
|
|
"sys_admin",
|
|
"sys_boot",
|
|
"sys_nice",
|
|
"sys_resource",
|
|
"sys_time",
|
|
"sys_tty_config",
|
|
"mknod",
|
|
"lease",
|
|
"audit_write",
|
|
"audit_control",
|
|
"setfcap",
|
|
"mac_override"
|
|
"syslog",
|
|
};
|
|
|
|
const char *capability_to_name(unsigned int cap)
|
|
{
|
|
const char *capname;
|
|
|
|
capname = (cap < (sizeof(capnames) / sizeof(char *))
|
|
? capnames[cap] : "invalid-capability");
|
|
|
|
return capname;
|
|
}
|
|
|
|
void __debug_capabilities(uint64_t capset, const char *name)
|
|
{
|
|
unsigned int i;
|
|
|
|
printf("%s:", name);
|
|
for (i = 0; i < (sizeof(capnames)/sizeof(char *)); i++) {
|
|
if (((1ull << i) & capset) != 0) {
|
|
printf (" %s", capability_to_name(i));
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
struct value_list *new_value_list(char *value)
|
|
{
|
|
struct value_list *val = (struct value_list *) calloc(1, sizeof(struct value_list));
|
|
if (val)
|
|
val->value = value;
|
|
return val;
|
|
}
|
|
|
|
void free_value_list(struct value_list *list)
|
|
{
|
|
struct value_list *next;
|
|
|
|
while (list) {
|
|
next = list->next;
|
|
if (list->value)
|
|
free(list->value);
|
|
free(list);
|
|
list = next;
|
|
}
|
|
}
|
|
|
|
struct value_list *dup_value_list(struct value_list *list)
|
|
{
|
|
struct value_list *entry, *dup, *head = NULL;
|
|
char *value;
|
|
|
|
list_for_each(list, entry) {
|
|
value = NULL;
|
|
if (list->value) {
|
|
value = strdup(list->value);
|
|
if (!value)
|
|
goto fail2;
|
|
}
|
|
dup = new_value_list(value);
|
|
if (!dup)
|
|
goto fail;
|
|
if (head)
|
|
list_append(head, dup);
|
|
else
|
|
head = dup;
|
|
}
|
|
|
|
return head;
|
|
|
|
fail:
|
|
free(value);
|
|
fail2:
|
|
free_value_list(head);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void print_value_list(struct value_list *list)
|
|
{
|
|
struct value_list *entry;
|
|
|
|
if (!list)
|
|
return;
|
|
|
|
fprintf(stderr, "%s", list->value);
|
|
list = list->next;
|
|
list_for_each(list, entry) {
|
|
fprintf(stderr, ", %s", entry->value);
|
|
}
|
|
}
|
|
|
|
void move_conditional_value(const char *rulename, char **dst_ptr,
|
|
struct cond_entry *cond_ent)
|
|
{
|
|
if (*dst_ptr)
|
|
yyerror("%s conditional \"%s\" can only be specified once\n",
|
|
rulename, cond_ent->name);
|
|
|
|
*dst_ptr = cond_ent->vals->value;
|
|
cond_ent->vals->value = NULL;
|
|
}
|
|
|
|
struct cond_entry *new_cond_entry(char *name, int eq, struct value_list *list)
|
|
{
|
|
struct cond_entry *ent = (struct cond_entry *) calloc(1, sizeof(struct cond_entry));
|
|
if (ent) {
|
|
ent->name = name;
|
|
ent->vals = list;
|
|
ent->eq = eq;
|
|
}
|
|
|
|
return ent;
|
|
}
|
|
|
|
void free_cond_entry(struct cond_entry *ent)
|
|
{
|
|
if (ent) {
|
|
free(ent->name);
|
|
free_value_list(ent->vals);
|
|
free(ent);
|
|
}
|
|
}
|
|
|
|
void free_cond_list(struct cond_entry *ents)
|
|
{
|
|
struct cond_entry *entry, *tmp;
|
|
|
|
list_for_each_safe(ents, entry, tmp) {
|
|
free_cond_entry(entry);
|
|
}
|
|
}
|
|
|
|
void print_cond_entry(struct cond_entry *ent)
|
|
{
|
|
if (ent) {
|
|
fprintf(stderr, "%s=(", ent->name);
|
|
print_value_list(ent->vals);
|
|
fprintf(stderr, ")\n");
|
|
}
|
|
}
|
|
|
|
#ifdef UNIT_TEST
|
|
|
|
#include "unit_test.h"
|
|
|
|
int test_str_to_boolean(void)
|
|
{
|
|
int rc = 0;
|
|
int retval;
|
|
|
|
retval = str_to_boolean("TRUE");
|
|
MY_TEST(retval == 1, "str2bool for TRUE");
|
|
|
|
retval = str_to_boolean("TrUe");
|
|
MY_TEST(retval == 1, "str2bool for TrUe");
|
|
|
|
retval = str_to_boolean("false");
|
|
MY_TEST(retval == 0, "str2bool for false");
|
|
|
|
retval = str_to_boolean("flase");
|
|
MY_TEST(retval == -1, "str2bool for flase");
|
|
|
|
return rc;
|
|
}
|
|
|
|
int test_processunquoted(void)
|
|
{
|
|
int rc = 0;
|
|
const char *teststring;
|
|
const char *resultstring;
|
|
|
|
teststring = "";
|
|
MY_TEST(strcmp(teststring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on empty string");
|
|
|
|
teststring = "\\1";
|
|
resultstring = "\001";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on one digit octal");
|
|
|
|
teststring = "\\8";
|
|
resultstring = "\\8";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on invalid octal digit \\8");
|
|
|
|
teststring = "\\18";
|
|
resultstring = "\0018";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on one digit octal followed by invalid octal digit");
|
|
|
|
teststring = "\\1a";
|
|
resultstring = "\001a";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on one digit octal followed by hex digit a");
|
|
|
|
teststring = "\\1z";
|
|
resultstring = "\001z";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on one digit octal follow by char z");
|
|
|
|
teststring = "\\11";
|
|
resultstring = "\011";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on two digit octal");
|
|
|
|
teststring = "\\118";
|
|
resultstring = "\0118";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on two digit octal followed by invalid octal digit");
|
|
|
|
teststring = "\\11a";
|
|
resultstring = "\011a";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on two digit octal followed by hex digit a");
|
|
|
|
teststring = "\\11z";
|
|
resultstring = "\011z";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on two digit octal followed by char z");
|
|
|
|
teststring = "\\111";
|
|
resultstring = "\111";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on three digit octal");
|
|
|
|
teststring = "\\378";
|
|
resultstring = "\0378";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on three digit octal two large, taken as 2 digit octal plus trailing char");
|
|
|
|
teststring = "123\\421123";
|
|
resultstring = "123\0421123";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on two character octal followed by valid octal digit \\421");
|
|
|
|
teststring = "123\\109123";
|
|
resultstring = "123\109123";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on octal 109");
|
|
|
|
teststring = "123\\1089123";
|
|
resultstring = "123\1089123";
|
|
MY_TEST(strcmp(resultstring, processunquoted(teststring, strlen(teststring))) == 0,
|
|
"processunquoted on octal 108");
|
|
|
|
return rc;
|
|
}
|
|
|
|
int test_processquoted(void)
|
|
{
|
|
int rc = 0;
|
|
const char *teststring, *processedstring;
|
|
char *out;
|
|
|
|
teststring = "";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(teststring, out) == 0,
|
|
"processquoted on empty string");
|
|
free(out);
|
|
|
|
teststring = "\"abcdefg\"";
|
|
processedstring = "abcdefg";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted on simple string");
|
|
free(out);
|
|
|
|
teststring = "\"abcd\\tefg\"";
|
|
processedstring = "abcd\tefg";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted on string with tab");
|
|
free(out);
|
|
|
|
teststring = "\"abcdefg\\\"";
|
|
processedstring = "abcdefg\\";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted on trailing slash");
|
|
free(out);
|
|
|
|
teststring = "\"a\\\\bcdefg\"";
|
|
processedstring = "a\\\\bcdefg";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted on quoted slash");
|
|
free(out);
|
|
|
|
teststring = "\"a\\\"bcde\\\"fg\"";
|
|
processedstring = "a\"bcde\"fg";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted on quoted quotes");
|
|
free(out);
|
|
|
|
teststring = "\"\\rabcdefg\"";
|
|
processedstring = "\rabcdefg";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted on quoted \\r");
|
|
free(out);
|
|
|
|
teststring = "\"abcdefg\\n\"";
|
|
processedstring = "abcdefg\n";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted on quoted \\n");
|
|
free(out);
|
|
|
|
teststring = "\"\\Uabc\\Ndefg\\x\"";
|
|
processedstring = "\\Uabc\\Ndefg\\x";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted passthrough on invalid quoted chars");
|
|
free(out);
|
|
|
|
teststring = "\"abc\\042defg\"";
|
|
processedstring = "abc\"defg";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted on quoted octal \\042");
|
|
free(out);
|
|
|
|
teststring = "\"abcdefg\\176\"";
|
|
processedstring = "abcdefg~";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted on quoted octal \\176");
|
|
free(out);
|
|
|
|
teststring = "\"abc\\429defg\"";
|
|
processedstring = "abc\0429defg";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted passthrough quoted invalid octal \\429");
|
|
free(out);
|
|
|
|
teststring = "\"abcdefg\\4\"";
|
|
processedstring = "abcdefg\004";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted passthrough quoted one digit trailing octal \\4");
|
|
|
|
teststring = "\"abcdefg\\04\"";
|
|
processedstring = "abcdefg\004";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted passthrough quoted two digit trailing octal \\04");
|
|
|
|
teststring = "\"abcdefg\\004\"";
|
|
processedstring = "abcdefg\004";
|
|
out = processquoted(teststring, strlen(teststring));
|
|
MY_TEST(strcmp(processedstring, out) == 0,
|
|
"processquoted passthrough quoted three digit trailing octal \\004");
|
|
free(out);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int rc = 0;
|
|
int retval;
|
|
|
|
retval = test_str_to_boolean();
|
|
if (retval != 0)
|
|
rc = retval;
|
|
|
|
retval = test_processunquoted();
|
|
if (retval != 0)
|
|
rc = retval;
|
|
|
|
retval = test_processquoted();
|
|
if (retval != 0)
|
|
rc = retval;
|
|
|
|
return rc;
|
|
}
|
|
#endif /* UNIT_TEST */
|