2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 10:07:12 +00:00

Merge ipc message queue rule support

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/858
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
This commit is contained in:
John Johansen 2022-12-05 03:04:48 +00:00
commit b5f558962f
89 changed files with 2505 additions and 20 deletions

5
.gitignore vendored
View File

@ -39,6 +39,7 @@ parser/libapparmor_re/hfa.o
parser/libapparmor_re/libapparmor_re.a parser/libapparmor_re/libapparmor_re.a
parser/libapparmor_re/parse.o parser/libapparmor_re/parse.o
parser/mount.o parser/mount.o
parser/mqueue.o
parser/network.o parser/network.o
parser/parser_alias.o parser/parser_alias.o
parser/parser_common.o parser/parser_common.o
@ -265,6 +266,8 @@ tests/regression/apparmor/open
tests/regression/apparmor/openat tests/regression/apparmor/openat
tests/regression/apparmor/pipe tests/regression/apparmor/pipe
tests/regression/apparmor/pivot_root tests/regression/apparmor/pivot_root
tests/regression/apparmor/posix_mq_rcv
tests/regression/apparmor/posix_mq_snd
tests/regression/apparmor/ptrace tests/regression/apparmor/ptrace
tests/regression/apparmor/ptrace_helper tests/regression/apparmor/ptrace_helper
tests/regression/apparmor/pwrite tests/regression/apparmor/pwrite
@ -288,6 +291,8 @@ tests/regression/apparmor/syscall_setpriority
tests/regression/apparmor/syscall_setscheduler tests/regression/apparmor/syscall_setscheduler
tests/regression/apparmor/syscall_sysctl tests/regression/apparmor/syscall_sysctl
tests/regression/apparmor/sysctl_proc tests/regression/apparmor/sysctl_proc
tests/regression/apparmor/sysv_mq_rcv
tests/regression/apparmor/sysv_mq_snd
tests/regression/apparmor/tcp tests/regression/apparmor/tcp
tests/regression/apparmor/transition tests/regression/apparmor/transition
tests/regression/apparmor/unix_fd_client tests/regression/apparmor/unix_fd_client

View File

@ -0,0 +1 @@
Apr 05 19:36:19 ubuntu kernel: audit: type=1400 audit(1649187379.660:255): apparmor="DENIED" operation="create" profile="/root/apparmor/tests/regression/apparmor/posix_mq_rcv" name="/queuename" pid=791 comm="posix_mq_rcv" requested="create" denied="create" class="posix_mqueue" fsuid=0 ouid=0

View File

@ -0,0 +1,16 @@
START
File: testcase_mqueue_01.in
Event type: AA_RECORD_DENIED
Audit ID: 1649187379.660:255
Operation: create
Mask: create
Denied Mask: create
fsuid: 0
ouid: 0
Profile: /root/apparmor/tests/regression/apparmor/posix_mq_rcv
Name: /queuename
Command: posix_mq_rcv
PID: 791
Class: posix_mqueue
Epoch: 1649187379
Audit subid: 255

View File

@ -0,0 +1,4 @@
/root/apparmor/tests/regression/apparmor/posix_mq_rcv {
mqueue create type=posix /queuename,
}

View File

@ -0,0 +1,2 @@
Apr 05 19:36:29 ubuntu kernel: audit: type=1400 audit(1649187389.828:262): apparmor="DENIED" operation="open" profile="/root/apparmor/tests/regression/apparmor/posix_mq_rcv" name="/queuename" pid=848 comm="posix_mq_rcv" requested="read create" denied="read" class="posix_mqueue" fsuid=0 ouid=0

View File

@ -0,0 +1,16 @@
START
File: testcase_mqueue_02.in
Event type: AA_RECORD_DENIED
Audit ID: 1649187389.828:262
Operation: open
Mask: read create
Denied Mask: read
fsuid: 0
ouid: 0
Profile: /root/apparmor/tests/regression/apparmor/posix_mq_rcv
Name: /queuename
Command: posix_mq_rcv
PID: 848
Class: posix_mqueue
Epoch: 1649187389
Audit subid: 262

View File

@ -0,0 +1,4 @@
/root/apparmor/tests/regression/apparmor/posix_mq_rcv {
mqueue read type=posix /queuename,
}

View File

@ -0,0 +1 @@
Apr 05 19:36:39 ubuntu kernel: audit: type=1400 audit(1649187399.973:265): apparmor="DENIED" operation="unlink" profile="/root/apparmor/tests/regression/apparmor/posix_mq_rcv" name="/queuename" pid=897 comm="posix_mq_rcv" requested="delete" denied="delete" class="posix_mqueue" fsuid=0 ouid=0

View File

@ -0,0 +1,16 @@
START
File: testcase_mqueue_03.in
Event type: AA_RECORD_DENIED
Audit ID: 1649187399.973:265
Operation: unlink
Mask: delete
Denied Mask: delete
fsuid: 0
ouid: 0
Profile: /root/apparmor/tests/regression/apparmor/posix_mq_rcv
Name: /queuename
Command: posix_mq_rcv
PID: 897
Class: posix_mqueue
Epoch: 1649187399
Audit subid: 265

View File

@ -0,0 +1,4 @@
/root/apparmor/tests/regression/apparmor/posix_mq_rcv {
mqueue delete type=posix /queuename,
}

View File

@ -0,0 +1 @@
Jun 02 16:58:20 ubuntu kernel: audit: type=1400 audit(1654189100.680:1011): apparmor="DENIED" operation="sysv_mqueue" profile="/root/apparmor/tests/regression/apparmor/sysv_mq_rcv" name="123" pid=13574 comm="sysv_mq_rcv" requested="create" denied="create" class="sysv_mqueue" fsuid=0 ouid=0

View File

@ -0,0 +1,16 @@
START
File: testcase_mqueue_04.in
Event type: AA_RECORD_DENIED
Audit ID: 1654189100.680:1011
Operation: sysv_mqueue
Mask: create
Denied Mask: create
fsuid: 0
ouid: 0
Profile: /root/apparmor/tests/regression/apparmor/sysv_mq_rcv
Name: 123
Command: sysv_mq_rcv
PID: 13574
Class: sysv_mqueue
Epoch: 1654189100
Audit subid: 1011

View File

@ -0,0 +1,4 @@
/root/apparmor/tests/regression/apparmor/sysv_mq_rcv {
mqueue create type=sysv 123,
}

View File

@ -0,0 +1 @@
Jun 02 17:15:45 ubuntu kernel: audit: type=1400 audit(1654190145.439:1135): apparmor="DENIED" operation="sysv_mqueue" profile="/root/apparmor/tests/regression/apparmor/sysv_mq_snd" name="123" pid=15849 comm="sysv_mq_snd" requested="open" denied="open" class="sysv_mqueue"

View File

@ -0,0 +1,14 @@
START
File: testcase_mqueue_05.in
Event type: AA_RECORD_DENIED
Audit ID: 1654190145.439:1135
Operation: sysv_mqueue
Mask: open
Denied Mask: open
Profile: /root/apparmor/tests/regression/apparmor/sysv_mq_snd
Name: 123
Command: sysv_mq_snd
PID: 15849
Class: sysv_mqueue
Epoch: 1654190145
Audit subid: 1135

View File

@ -0,0 +1,4 @@
/root/apparmor/tests/regression/apparmor/sysv_mq_snd {
mqueue open type=sysv 123,
}

View File

@ -0,0 +1 @@
Jun 02 17:15:37 ubuntu kernel: audit: type=1400 audit(1654190137.559:1122): apparmor="DENIED" operation="sysv_mqueue" profile="/root/apparmor/tests/regression/apparmor/sysv_mq_rcv" name="123" pid=15632 comm="sysv_mq_rcv" requested="read" denied="read" class="sysv_mqueue" fsuid=0 ouid=0

View File

@ -0,0 +1,16 @@
START
File: testcase_mqueue_06.in
Event type: AA_RECORD_DENIED
Audit ID: 1654190137.559:1122
Operation: sysv_mqueue
Mask: read
Denied Mask: read
fsuid: 0
ouid: 0
Profile: /root/apparmor/tests/regression/apparmor/sysv_mq_rcv
Name: 123
Command: sysv_mq_rcv
PID: 15632
Class: sysv_mqueue
Epoch: 1654190137
Audit subid: 1122

View File

@ -0,0 +1,4 @@
/root/apparmor/tests/regression/apparmor/sysv_mq_rcv {
mqueue read type=sysv 123,
}

View File

@ -0,0 +1 @@
Jun 02 17:15:51 ubuntu kernel: audit: type=1400 audit(1654190151.003:1145): apparmor="DENIED" operation="sysv_mqueue" profile="/root/apparmor/tests/regression/apparmor/sysv_mq_rcv" name="123" pid=15973 comm="sysv_mq_rcv" requested="delete" denied="delete" class="sysv_mqueue" fsuid=1001 ouid=1001

View File

@ -0,0 +1,16 @@
START
File: testcase_mqueue_07.in
Event type: AA_RECORD_DENIED
Audit ID: 1654190151.003:1145
Operation: sysv_mqueue
Mask: delete
Denied Mask: delete
fsuid: 1001
ouid: 1001
Profile: /root/apparmor/tests/regression/apparmor/sysv_mq_rcv
Name: 123
Command: sysv_mq_rcv
PID: 15973
Class: sysv_mqueue
Epoch: 1654190151
Audit subid: 1145

View File

@ -0,0 +1,4 @@
/root/apparmor/tests/regression/apparmor/sysv_mq_rcv {
mqueue delete type=sysv 123,
}

View File

@ -0,0 +1 @@
Jun 02 17:15:55 ubuntu kernel: audit: type=1400 audit(1654190155.699:1155): apparmor="DENIED" operation="sysv_mqueue" profile="/root/apparmor/tests/regression/apparmor/sysv_mq_snd" name="123" pid=16148 comm="sysv_mq_snd" requested="write" denied="write" class="sysv_mqueue" fsuid=1001 ouid=1001

View File

@ -0,0 +1,16 @@
START
File: testcase_mqueue_08.in
Event type: AA_RECORD_DENIED
Audit ID: 1654190155.699:1155
Operation: sysv_mqueue
Mask: write
Denied Mask: write
fsuid: 1001
ouid: 1001
Profile: /root/apparmor/tests/regression/apparmor/sysv_mq_snd
Name: 123
Command: sysv_mq_snd
PID: 16148
Class: sysv_mqueue
Epoch: 1654190155
Audit subid: 1155

View File

@ -0,0 +1,4 @@
/root/apparmor/tests/regression/apparmor/sysv_mq_snd {
mqueue write type=sysv 123,
}

View File

@ -101,10 +101,11 @@ SRCS = parser_common.c parser_include.c parser_interface.c parser_lex.c \
parser_yacc.c parser_regex.c parser_variable.c parser_policy.c \ parser_yacc.c parser_regex.c parser_variable.c parser_policy.c \
parser_alias.c common_optarg.c lib.c network.c \ parser_alias.c common_optarg.c lib.c network.c \
mount.cc dbus.cc profile.cc rule.cc signal.cc ptrace.cc \ mount.cc dbus.cc profile.cc rule.cc signal.cc ptrace.cc \
af_rule.cc af_unix.cc policy_cache.c default_features.c userns.cc af_rule.cc af_unix.cc policy_cache.c default_features.c userns.cc \
mqueue.cc
HDRS = parser.h parser_include.h immunix.h mount.h dbus.h lib.h profile.h \ HDRS = parser.h parser_include.h immunix.h mount.h dbus.h lib.h profile.h \
rule.h common_optarg.h signal.h ptrace.h network.h af_rule.h af_unix.h \ rule.h common_optarg.h signal.h ptrace.h network.h af_rule.h af_unix.h \
policy_cache.h file_cache.h userns.h policy_cache.h file_cache.h userns.h mqueue.h
TOOLS = apparmor_parser TOOLS = apparmor_parser
OBJECTS = $(patsubst %.cc, %.o, $(SRCS:.c=.o)) OBJECTS = $(patsubst %.cc, %.o, $(SRCS:.c=.o))
@ -304,6 +305,9 @@ rule.o: rule.cc rule.h policydb.h
userns.o: userns.cc userns.h parser.h parser_yacc.h rule.h $(APPARMOR_H) userns.o: userns.cc userns.h parser.h parser_yacc.h rule.h $(APPARMOR_H)
$(CXX) $(EXTRA_CFLAGS) -c -o $@ $< $(CXX) $(EXTRA_CFLAGS) -c -o $@ $<
mqueue.o: mqueue.cc mqueue.h parser.h immunix.h profile.h parser_yacc.h rule.h $(APPARMOR_H)
$(CXX) $(EXTRA_CFLAGS) -c -o $@ $<
parser_version.h: Makefile parser_version.h: Makefile
@echo \#define PARSER_VERSION \"$(VERSION)\" > .ver @echo \#define PARSER_VERSION \"$(VERSION)\" > .ver
@mv -f .ver $@ @mv -f .ver $@

View File

@ -123,7 +123,7 @@ B<RULES> = [ ( I<LINE RULES> | I<COMMA RULES> ',' | I<BLOCK RULES> )
B<LINE RULES> = ( I<COMMENT> | I<INCLUDE> ) [ '\r' ] '\n' B<LINE RULES> = ( I<COMMENT> | I<INCLUDE> ) [ '\r' ] '\n'
B<COMMA RULES> = ( I<CAPABILITY RULE> | I<NETWORK RULE> | I<MOUNT RULE> | I<PIVOT ROOT RULE> | I<UNIX RULE> | I<FILE RULE> | I<LINK RULE> | I<CHANGE_PROFILE RULE> | I<RLIMIT RULE> | I<DBUS RULE> ) B<COMMA RULES> = ( I<CAPABILITY RULE> | I<NETWORK RULE> | I<MOUNT RULE> | I<PIVOT ROOT RULE> | I<UNIX RULE> | I<FILE RULE> | I<LINK RULE> | I<CHANGE_PROFILE RULE> | I<RLIMIT RULE> | I<DBUS RULE> | I<MQUEUE RULE> )
B<BLOCK RULES> = ( I<SUBPROFILE> | I<HAT> | I<QUALIFIER BLOCK> ) B<BLOCK RULES> = ( I<SUBPROFILE> | I<HAT> | I<QUALIFIER BLOCK> )
@ -176,6 +176,20 @@ B<MOUNT FLAGS> = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec'
B<MOUNT EXPRESSION> = ( I<ALPHANUMERIC> | I<AARE> ) ... B<MOUNT EXPRESSION> = ( I<ALPHANUMERIC> | I<AARE> ) ...
B<MQUEUE_RULE> = [ I<QUALIFIERS> ] 'mqueue' [ I<MQUEUE ACCESS PERMISSIONS> ] [ I<MQUEUE TYPE> ] [ I<MQUEUE LABEL> ] [ I<MQUEUE NAME> ]
B<MQUEUE ACCESS PERMISSIONS> = I<MQUEUE ACCESS> | I<MQUEUE ACCESS LIST>
B<MQUEUE ACCESS LIST> = '(' Comma or space separated list of I<MQUEUE ACCESS> ')'
B<MQUEUE ACCESS> = ( 'r' | 'w' | 'rw' | 'read' | 'write' | 'create' | 'open' | 'delete' | 'getattr' | 'setattr' )
B<MQUEUE TYPE> = 'type' '=' ( 'posix' | 'sysv' )
B<MQUEUE LABEL> = 'label' '=' '(' '"' I<AARE> '"' | I<AARE> ')'
B<MQUEUE NAME> = I<AARE>
B<PIVOT ROOT RULE> = [ I<QUALIFIERS> ] pivot_root [ oldroot=I<OLD PUT FILEGLOB> ] [ I<NEW ROOT FILEGLOB> ] [ '-E<gt>' I<PROFILE NAME> ] B<PIVOT ROOT RULE> = [ I<QUALIFIERS> ] pivot_root [ oldroot=I<OLD PUT FILEGLOB> ] [ I<NEW ROOT FILEGLOB> ] [ '-E<gt>' I<PROFILE NAME> ]
B<SOURCE FILEGLOB> = I<FILEGLOB> B<SOURCE FILEGLOB> = I<FILEGLOB>
@ -1057,6 +1071,51 @@ Matches only:
=back =back
=head2 Message Queue rules
AppArmor supports mediation of POSIX and SYSV message queues.
AppArmor Message Queue permissions are implied when a rule does not explicitly
state an access list. By default, all Message Queue permissions are implied.
AppArmor Message Queue permissions become more restricted as further information
is specified. Policy can be specified by determining its access mode, type,
label, and message queue name.
Regarding access modes, 'r' and 'read' are used to read messages from the queue.
'w' and 'write' are used to write to the message queue. 'create' is used to create
the message queue, and 'open' is used to get the message queue identifier when the
queue is already created. 'delete' is used to remove the message queue. The access
modes to get and set attributes of the message queue are 'setattr' and 'getattr'.
The type of the policy can be either 'posix' or 'sysv'. This information is
relevant when the message queue name is not specified, and when specified can be
inferred by the queue name, since message queues' name for posix must start with '/',
and message queues' key for SYSV must be a positive integer.
The policy label is the label assigned to the message queue when it is created.
The message queue name can be either a string starting with '/' if the type
is POSIX, or a positive integer if the type is SYSV. If the type is not
specified, then it will be inferred by the queue name.
Example AppArmor Message Queue rules:
# Allow all Message Queue access
mqueue,
# Explicitly allow all Message Queue access,
mqueue (create, open, delete, read, write, getattr, setattr),
# Explicitly deny use of Message Queue
deny mqueue,
# Allow all access for POSIX queue of name /bar
mqueue type=posix /bar,
# Allow create permission for a SYSV queue of label foo
mqueue create label=foo 123,
=head2 Pivot Root Rules =head2 Pivot Root Rules
AppArmor mediates changing of the root filesystem through the pivot_root(2) AppArmor mediates changing of the root filesystem through the pivot_root(2)

278
parser/mqueue.cc Normal file
View File

@ -0,0 +1,278 @@
/*
* Copyright (c) 2022
* 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 "parser.h"
#include "profile.h"
#include "mqueue.h"
#include <iomanip>
#include <string>
#include <iostream>
#include <sstream>
int parse_mqueue_mode(const char *str_mode, int *mode, int fail)
{
return parse_X_mode("mqueue", AA_VALID_MQUEUE_PERMS, str_mode, mode, fail);
}
static bool is_all_digits(char *str)
{
const char *s = str;
while (*str && isdigit(*str))
str++;
return str != s && *str == 0;
}
void mqueue_rule::validate_qname(void)
{
if (qname[0] == '/') {
// TODO full syntax check of name
if (qtype == mqueue_sysv)
yyerror("mqueue type=sysv invalid name '%s', sysv "
"message queues must be identified by a "
"positive integer.\n", qname);
qtype = mqueue_posix; // implied by name
} else if (is_all_digits(qname)) {
if (qtype == mqueue_posix)
yyerror("mqueue type=posix invalid name '%s', posix "
"message queues names must begin with a /\n",
qname);
qtype = mqueue_sysv; // implied
} else {
yyerror("mqueue invalid name '%s', message queue names must begin with a / or be a positive integer.\n", qname);
}
}
void mqueue_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 mqueue rules\n");
if (strcmp(cond_ent->name, "label") == 0) {
move_conditional_value("mqueue", &label, cond_ent);
} else if (strcmp(cond_ent->name, "type") == 0) {
char *tmp = NULL;
move_conditional_value("mqueue", &tmp, cond_ent);
if (strcmp(tmp, "posix") == 0)
qtype = mqueue_posix;
else if (strcmp(tmp, "sysv") == 0)
qtype = mqueue_sysv;
else
yyerror("mqueue invalid type='%s'\n", tmp);
free(tmp);
} else {
yyerror("invalid mqueue rule conditional \"%s\"\n",
cond_ent->name);
}
}
}
mqueue_rule::mqueue_rule(int mode_p, struct cond_entry *conds, char *qname_p):
qtype(mqueue_unspecified), qname(qname_p), label(NULL), audit(0), deny(0)
{
move_conditionals(conds);
free_cond_list(conds);
if (qname)
validate_qname();
if (mode_p) {
// do we want to allow perms to imply type like we do for
// qname?
if (qtype == mqueue_posix && (mode_p & ~AA_VALID_POSIX_MQ_PERMS)) {
yyerror("mode contains invalid permissions for mqueue type=posix\n");
} else if (qtype == mqueue_sysv && (mode_p & ~AA_VALID_SYSV_MQ_PERMS)) {
yyerror("mode contains invalid permissions for mqueue type=sysv\n");
} else if (mode_p & ~AA_VALID_MQUEUE_PERMS) {
yyerror("mode contains invalid permissions for mqueue\n");
}
mode = mode_p;
} else {
// default to all perms
mode = AA_VALID_MQUEUE_PERMS;
}
qname = qname_p;
}
ostream &mqueue_rule::dump(ostream &os)
{
if (audit)
os << "audit ";
if (deny)
os << "deny ";
os << "mqueue ";
// do we want to always put type out or leave it implied if there
// is a qname
if (qtype == mqueue_posix)
os << "type=posix";
else if (qtype == mqueue_sysv)
os << "type=sysv";
if (mode != AA_VALID_MQUEUE_PERMS) {
os << "(";
if (mode & AA_MQUEUE_WRITE)
os << "write ";
if (mode & AA_MQUEUE_READ)
os << "read ";
if (mode & AA_MQUEUE_OPEN)
os << "open ";
if (mode & AA_MQUEUE_CREATE)
os << "create ";
if (mode & AA_MQUEUE_DELETE)
os << "delete ";
if (mode & AA_MQUEUE_SETATTR)
os << "setattr ";
if (mode & AA_MQUEUE_GETATTR)
os << "getattr ";
os << ")";
}
if (qname)
os << " " << qname;
os << ",\n";
return os;
}
int mqueue_rule::expand_variables(void)
{
int error = expand_entry_variables(&qname);
if (error)
return error;
error = expand_entry_variables(&label);
if (error)
return error;
return 0;
}
/* TODO: this is not right, need separate warning for each type */
void mqueue_rule::warn_once(const char *name)
{
if (qtype == mqueue_unspecified)
rule_t::warn_once(name, "mqueue rules not enforced");
else if (qtype == mqueue_posix)
rule_t::warn_once(name, "mqueue type=posix rules not enforced");
else if (qtype == mqueue_sysv)
rule_t::warn_once(name, "mqueue type=sysv rules not enforced");
}
int mqueue_rule::gen_policy_re(Profile &prof)
{
std::string labelbuf;
std::string buf;
const int size = 2;
const char *vec[size];
if (qtype == mqueue_posix && !features_supports_posix_mqueue) {
warn_once(prof.name);
return RULE_NOT_SUPPORTED;
} else if (qtype == mqueue_sysv && !features_supports_sysv_mqueue) {
warn_once(prof.name);
// return RULE_NOT_SUPPORTED;
} else if (qtype == mqueue_unspecified &&
!(features_supports_posix_mqueue ||
features_supports_sysv_mqueue)) {
warn_once(prof.name);
// should split into warning where posix and sysv can
// be separated from nothing being enforced
// return RULE_NOT_SUPPORTED;
}
prof.flags.flags |= FLAG_DEBUG1;
/* always generate a label and mqueue entry */
//buffer << "(" << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_LABEL << "|)"; //is this required?
// posix and generic
if (qtype != mqueue_sysv) {
std::ostringstream buffer;
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_POSIX_MQUEUE;
buf.assign(buffer.str());
if (qname) {
if (!convert_entry(buf, qname))
goto fail;
} else {
buf += default_match_pattern;
}
vec[0] = buf.c_str();
if (label) {
if (!convert_entry(labelbuf, label))
goto fail;
vec[1] = labelbuf.c_str();
} else {
vec[1] = anyone_match_pattern;
}
if (mode & AA_VALID_POSIX_MQ_PERMS) {
/* store perms at name match so label doesn't need
* to be checked
*/
if (!label && !prof.policy.rules->add_rule_vec(deny, mode, audit, 1, vec, dfaflags, false))
goto fail;
/* also provide label match with perm */
if (!prof.policy.rules->add_rule_vec(deny, mode, audit, size, vec, dfaflags, false))
goto fail;
}
}
// sysv and generic
if (qtype != mqueue_posix) {
std::ostringstream buffer;
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_SYSV_MQUEUE;
buf.assign(buffer.str());
if (qname) {
if (!convert_entry(buf, qname))
goto fail;
} else {
buf += default_match_pattern;
}
vec[0] = buf.c_str();
if (label) {
if (!convert_entry(labelbuf, label))
goto fail;
vec[1] = labelbuf.c_str();
} else {
vec[1] = anyone_match_pattern;
}
if (mode & AA_VALID_SYSV_MQ_PERMS) {
if (!label && !prof.policy.rules->add_rule_vec(deny, mode, audit, 1, vec, dfaflags, false))
goto fail;
/* also provide label match with perm */
if (!prof.policy.rules->add_rule_vec(deny, mode, audit, size, vec, dfaflags, false))
goto fail;
}
}
return RULE_OK;
fail:
return RULE_ERROR;
}

111
parser/mqueue.h Normal file
View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2022
* 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.
*/
/* sysv and posix mqueue mediation. */
#ifndef __AA_MQUEUE_H
#define __AA_MQUEUE_H
#include "immunix.h"
#include "parser.h"
#define AA_MQUEUE_WRITE AA_MAY_WRITE
#define AA_MQUEUE_READ AA_MAY_READ
#define AA_MQUEUE_CREATE 0x0010 /* create */
#define AA_MQUEUE_DELETE 0x0020 /* destroy, unlink */
#define AA_MQUEUE_OPEN 0x0040 /* associate */
#define AA_MQUEUE_RENAME 0x0080 /* ?? pair */
#define AA_MQUEUE_SETATTR 0x0100 /* setattr */
#define AA_MQUEUE_GETATTR 0x0200 /* getattr */
#define AA_MQUEUE_CHMOD 0x1000 /* pair */
#define AA_MQUEUE_CHOWN 0x2000 /* pair */
#define AA_MQUEUE_CHGRP 0x4000 /* pair */
#define AA_MQUEUE_LOCK 0x8000 /* LINK_SUBSET overlaid */
/* sysv and posix mqueues use different terminology, allow mapping
* between. To be as common as possible.
*
* sysv and posix mqueues have different levels of mediation possible
* in the kernel. Only the most basic mqueue rules can be shared
* eg.
* mqueue rw,
* mqueue rw label=foo,
*
* kernel doesn't allow for us to control
* - posix
* - notify
* - getattr/setattr
* - labels at anything other than mqueue label, via mqueue inode.
*/
#define AA_VALID_POSIX_MQ_PERMS (AA_MQUEUE_WRITE | AA_MQUEUE_READ | \
AA_MQUEUE_CREATE | AA_MQUEUE_DELETE | \
AA_MQUEUE_OPEN)
/* TBD - for now make it wider than posix */
#define AA_VALID_SYSV_MQ_PERMS (AA_MQUEUE_WRITE | AA_MQUEUE_READ | \
AA_MQUEUE_CREATE | AA_MQUEUE_DELETE | \
AA_MQUEUE_OPEN | \
AA_MQUEUE_SETATTR | AA_MQUEUE_GETATTR)
#define AA_VALID_MQUEUE_PERMS (AA_VALID_POSIX_MQ_PERMS | \
AA_VALID_SYSV_MQ_PERMS)
// warning getting into overlap area
/* Type of mqueue - can be explicit or implied by rule id/path */
typedef enum mqueue_type {
mqueue_unspecified,
mqueue_posix,
mqueue_sysv
} mqueue_type;
int parse_mqueue_mode(const char *str_mode, int *mode, int fail);
class mqueue_rule: public rule_t {
void move_conditionals(struct cond_entry *conds);
public:
mqueue_type qtype;
char *qname;
char *label;
int mode;
int audit;
int deny;
mqueue_rule(int mode, struct cond_entry *conds, char *qname = NULL);
virtual ~mqueue_rule()
{
free(qname);
free(label);
};
virtual ostream &dump(ostream &os);
virtual int expand_variables(void);
virtual int gen_policy_re(Profile &prof);
virtual void post_process(Profile &prof unused) { };
protected:
virtual void warn_once(const char *name) override;
void validate_qname(void);
};
#endif /* __AA_MQUEUE_H */

View File

@ -345,6 +345,8 @@ extern int features_supports_unix;
extern int features_supports_stacking; extern int features_supports_stacking;
extern int features_supports_domain_xattr; extern int features_supports_domain_xattr;
extern int features_supports_userns; extern int features_supports_userns;
extern int features_supports_posix_mqueue;
extern int features_supports_sysv_mqueue;
extern int kernel_supports_oob; extern int kernel_supports_oob;
extern int conf_verbose; extern int conf_verbose;
extern int conf_quiet; extern int conf_quiet;

View File

@ -79,6 +79,8 @@ int features_supports_ptrace = 0; /* kernel supports ptrace rules */
int features_supports_stacking = 0; /* kernel supports stacking */ int features_supports_stacking = 0; /* kernel supports stacking */
int features_supports_domain_xattr = 0; /* x attachment cond */ int features_supports_domain_xattr = 0; /* x attachment cond */
int features_supports_userns = 0; /* kernel supports user namespace */ int features_supports_userns = 0; /* kernel supports user namespace */
int features_supports_posix_mqueue = 0; /* kernel supports mqueue rules */
int features_supports_sysv_mqueue = 0; /* kernel supports mqueue rules */
int kernel_supports_oob = 0; /* out of band transitions */ int kernel_supports_oob = 0; /* out of band transitions */
int conf_verbose = 0; int conf_verbose = 0;
int conf_quiet = 0; int conf_quiet = 0;

View File

@ -420,7 +420,7 @@ void sd_serialize_profile(std::ostringstream &buf, Profile *profile,
sd_write_struct(buf, "flags"); sd_write_struct(buf, "flags");
/* used to be flags.debug, but that's no longer supported */ /* used to be flags.debug, but that's no longer supported */
sd_write_uint32(buf, profile->flags.hat); sd_write_uint32(buf, profile->flags.flags);
sd_write_uint32(buf, profile_mode_packed(profile->flags.mode)); sd_write_uint32(buf, profile_mode_packed(profile->flags.mode));
sd_write_uint32(buf, profile->flags.audit); sd_write_uint32(buf, profile->flags.audit);
sd_write_structend(buf); sd_write_structend(buf);

View File

@ -328,6 +328,7 @@ GT >
%x INCLUDE_EXISTS %x INCLUDE_EXISTS
%x ABI_MODE %x ABI_MODE
%x USERNS_MODE %x USERNS_MODE
%x MQUEUE_MODE
%% %%
@ -340,7 +341,7 @@ GT >
} }
%} %}
<INITIAL,SUB_ID_WS,INCLUDE,INCLUDE_EXISTS,LIST_VAL_MODE,EXTCOND_MODE,LIST_COND_VAL,LIST_COND_PAREN_VAL,LIST_COND_MODE,EXTCONDLIST_MODE,ASSIGN_MODE,NETWORK_MODE,CHANGE_PROFILE_MODE,RLIMIT_MODE,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,ABI_MODE,USERNS_MODE>{ <INITIAL,SUB_ID_WS,INCLUDE,INCLUDE_EXISTS,LIST_VAL_MODE,EXTCOND_MODE,LIST_COND_VAL,LIST_COND_PAREN_VAL,LIST_COND_MODE,EXTCONDLIST_MODE,ASSIGN_MODE,NETWORK_MODE,CHANGE_PROFILE_MODE,RLIMIT_MODE,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,ABI_MODE,USERNS_MODE,MQUEUE_MODE>{
{WS}+ { DUMP_PREPROCESS; /* Ignoring whitespace */ } {WS}+ { DUMP_PREPROCESS; /* Ignoring whitespace */ }
} }
@ -376,7 +377,7 @@ GT >
yyterminate(); yyterminate();
} }
<INITIAL,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE>{ <INITIAL,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,MQUEUE_MODE>{
(peer|xattrs)/{WS}*={WS}*\( { (peer|xattrs)/{WS}*={WS}*\( {
/* we match to the = in the lexer so that we can switch scanner /* we match to the = in the lexer so that we can switch scanner
* state. By the time the parser see the = it may be too late * state. By the time the parser see the = it may be too late
@ -560,17 +561,25 @@ GT >
listen { RETURN_TOKEN(TOK_LISTEN); } listen { RETURN_TOKEN(TOK_LISTEN); }
accept { RETURN_TOKEN(TOK_ACCEPT); } accept { RETURN_TOKEN(TOK_ACCEPT); }
connect { RETURN_TOKEN(TOK_CONNECT); } connect { RETURN_TOKEN(TOK_CONNECT); }
getattr { RETURN_TOKEN(TOK_GETATTR); }
setattr { RETURN_TOKEN(TOK_SETATTR); }
getopt { RETURN_TOKEN(TOK_GETOPT); } getopt { RETURN_TOKEN(TOK_GETOPT); }
setopt { RETURN_TOKEN(TOK_SETOPT); } setopt { RETURN_TOKEN(TOK_SETOPT); }
shutdown { RETURN_TOKEN(TOK_SHUTDOWN); } shutdown { RETURN_TOKEN(TOK_SHUTDOWN); }
} }
<UNIX_MODE,USERNS_MODE>{ <UNIX_MODE,USERNS_MODE,MQUEUE_MODE>{
create { RETURN_TOKEN(TOK_CREATE); } create { RETURN_TOKEN(TOK_CREATE); }
} }
<MQUEUE_MODE>{
open { RETURN_TOKEN(TOK_OPEN); }
delete { RETURN_TOKEN(TOK_DELETE); }
}
<UNIX_MODE,MQUEUE_MODE>{
getattr { RETURN_TOKEN(TOK_GETATTR); }
setattr { RETURN_TOKEN(TOK_SETATTR); }
}
<DBUS_MODE,UNIX_MODE>{ <DBUS_MODE,UNIX_MODE>{
bind { RETURN_TOKEN(TOK_BIND); } bind { RETURN_TOKEN(TOK_BIND); }
} }
@ -590,7 +599,7 @@ GT >
tracedby { RETURN_TOKEN(TOK_TRACEDBY); } tracedby { RETURN_TOKEN(TOK_TRACEDBY); }
} }
<DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE>{ <DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,MQUEUE_MODE>{
read { RETURN_TOKEN(TOK_READ); } read { RETURN_TOKEN(TOK_READ); }
write { RETURN_TOKEN(TOK_WRITE); } write { RETURN_TOKEN(TOK_WRITE); }
{OPEN_PAREN} { {OPEN_PAREN} {
@ -606,7 +615,7 @@ GT >
{ARROW} { RETURN_TOKEN(TOK_ARROW); } {ARROW} { RETURN_TOKEN(TOK_ARROW); }
} }
<MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE>{ <MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,MQUEUE_MODE>{
({IDS_NOEQ}|{LABEL}|{QUOTED_ID}) { ({IDS_NOEQ}|{LABEL}|{QUOTED_ID}) {
yylval.id = processid(yytext, yyleng); yylval.id = processid(yytext, yyleng);
RETURN_TOKEN(TOK_ID); RETURN_TOKEN(TOK_ID);
@ -731,13 +740,16 @@ include/{WS} {
case TOK_USERNS: case TOK_USERNS:
state = USERNS_MODE; state = USERNS_MODE;
break; break;
case TOK_MQUEUE:
state = MQUEUE_MODE;
break;
default: /* nothing */ default: /* nothing */
break; break;
} }
PUSH_AND_RETURN(state, token); PUSH_AND_RETURN(state, token);
} }
<INITIAL,NETWORK_MODE,RLIMIT_MODE,CHANGE_PROFILE_MODE,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,ABI_MODE,USERNS_MODE>{ <INITIAL,NETWORK_MODE,RLIMIT_MODE,CHANGE_PROFILE_MODE,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,ABI_MODE,USERNS_MODE,MQUEUE_MODE>{
{END_OF_RULE} { {END_OF_RULE} {
if (YY_START != INITIAL) if (YY_START != INITIAL)
POP_NODUMP(); POP_NODUMP();
@ -745,14 +757,14 @@ include/{WS} {
} }
} }
<INITIAL,SUB_ID_WS,INCLUDE,INCLUDE_EXISTS,LIST_VAL_MODE,EXTCOND_MODE,LIST_COND_VAL,LIST_COND_PAREN_VAL,LIST_COND_MODE,EXTCONDLIST_MODE,NETWORK_MODE,CHANGE_PROFILE_MODE,RLIMIT_MODE,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,ABI_MODE,USERNS_MODE>{ <INITIAL,SUB_ID_WS,INCLUDE,INCLUDE_EXISTS,LIST_VAL_MODE,EXTCOND_MODE,LIST_COND_VAL,LIST_COND_PAREN_VAL,LIST_COND_MODE,EXTCONDLIST_MODE,NETWORK_MODE,CHANGE_PROFILE_MODE,RLIMIT_MODE,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,ABI_MODE,USERNS_MODE,MQUEUE_MODE>{
\r?\n { \r?\n {
DUMP_PREPROCESS; DUMP_PREPROCESS;
current_lineno++; current_lineno++;
} }
} }
<INITIAL,SUB_ID,SUB_ID_WS,SUB_VALUE,LIST_VAL_MODE,EXTCOND_MODE,LIST_COND_VAL,LIST_COND_PAREN_VAL,LIST_COND_MODE,EXTCONDLIST_MODE,ASSIGN_MODE,NETWORK_MODE,CHANGE_PROFILE_MODE,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,RLIMIT_MODE,INCLUDE,INCLUDE_EXISTS,ABI_MODE,USERNS_MODE>{ <INITIAL,SUB_ID,SUB_ID_WS,SUB_VALUE,LIST_VAL_MODE,EXTCOND_MODE,LIST_COND_VAL,LIST_COND_PAREN_VAL,LIST_COND_MODE,EXTCONDLIST_MODE,ASSIGN_MODE,NETWORK_MODE,CHANGE_PROFILE_MODE,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,RLIMIT_MODE,INCLUDE,INCLUDE_EXISTS,ABI_MODE,USERNS_MODE,MQUEUE_MODE>{
(.|\n) { (.|\n) {
DUMP_PREPROCESS; DUMP_PREPROCESS;
/* Something we didn't expect */ /* Something we didn't expect */
@ -788,4 +800,5 @@ unordered_map<int, string> state_names = {
STATE_TABLE_ENT(INCLUDE_EXISTS), STATE_TABLE_ENT(INCLUDE_EXISTS),
STATE_TABLE_ENT(ABI_MODE), STATE_TABLE_ENT(ABI_MODE),
STATE_TABLE_ENT(USERNS_MODE), STATE_TABLE_ENT(USERNS_MODE),
STATE_TABLE_ENT(MQUEUE_MODE),
}; };

View File

@ -944,6 +944,12 @@ void set_supported_features()
features_supports_userns = features_intersect(kernel_features, features_supports_userns = features_intersect(kernel_features,
policy_features, policy_features,
"namespaces/mask/userns_create"); "namespaces/mask/userns_create");
features_supports_posix_mqueue = features_intersect(kernel_features,
policy_features,
"ipc/posix_mqueue");
features_supports_sysv_mqueue = features_intersect(kernel_features,
policy_features,
"ipc/sysv_mqueue");
} }
static bool do_print_cache_dir(aa_features *features, int dirfd, const char *path) static bool do_print_cache_dir(aa_features *features, int dirfd, const char *path)

View File

@ -121,6 +121,9 @@ static struct keyword_table keyword_table[] = {
{"readby", TOK_READBY}, {"readby", TOK_READBY},
{"abi", TOK_ABI}, {"abi", TOK_ABI},
{"userns", TOK_USERNS}, {"userns", TOK_USERNS},
{"mqueue", TOK_MQUEUE},
{"delete", TOK_DELETE},
{"open", TOK_OPEN},
/* terminate */ /* terminate */
{NULL, 0} {NULL, 0}

View File

@ -243,7 +243,7 @@ void post_process_rule_entries(Profile *prof)
static int profile_add_hat_rules(Profile *prof) static int profile_add_hat_rules(Profile *prof)
{ {
/* don't add hat rules if not hat or profile doesn't have hats */ /* don't add hat rules if not hat or profile doesn't have hats */
if (!prof->flags.hat && prof->hat_table.empty()) if (!(prof->flags.flags & FLAG_HAT) && prof->hat_table.empty())
return 0; return 0;
if (!add_proc_access(prof, CHANGEHAT_PATH)) if (!add_proc_access(prof, CHANGEHAT_PATH))

View File

@ -936,6 +936,8 @@ static const char *mediates_extended_net = CLASS_STR(AA_CLASS_NET);
static const char *mediates_netv8 = CLASS_STR(AA_CLASS_NETV8); static const char *mediates_netv8 = CLASS_STR(AA_CLASS_NETV8);
static const char *mediates_net_unix = CLASS_SUB_STR(AA_CLASS_NET, AF_UNIX); static const char *mediates_net_unix = CLASS_SUB_STR(AA_CLASS_NET, AF_UNIX);
static const char *mediates_ns = CLASS_STR(AA_CLASS_NS); static const char *mediates_ns = CLASS_STR(AA_CLASS_NS);
static const char *mediates_posix_mqueue = CLASS_STR(AA_CLASS_POSIX_MQUEUE);
static const char *mediates_sysv_mqueue = CLASS_STR(AA_CLASS_SYSV_MQUEUE);
int process_profile_policydb(Profile *prof) int process_profile_policydb(Profile *prof)
{ {
@ -981,6 +983,12 @@ int process_profile_policydb(Profile *prof)
if (features_supports_userns && if (features_supports_userns &&
!prof->policy.rules->add_rule(mediates_ns, 0, AA_MAY_READ, 0, dfaflags)) !prof->policy.rules->add_rule(mediates_ns, 0, AA_MAY_READ, 0, dfaflags))
goto out; goto out;
if (features_supports_posix_mqueue &&
!prof->policy.rules->add_rule(mediates_posix_mqueue, 0, AA_MAY_READ, 0, dfaflags))
goto out;
if (features_supports_sysv_mqueue &&
!prof->policy.rules->add_rule(mediates_sysv_mqueue, 0, AA_MAY_READ, 0, dfaflags))
goto out;
if (prof->policy.rules->rule_count > 0) { if (prof->policy.rules->rule_count > 0) {
int xmatch_len = 0; int xmatch_len = 0;

View File

@ -143,6 +143,8 @@ void add_local_entry(Profile *prof);
%token TOK_READBY %token TOK_READBY
%token TOK_ABI %token TOK_ABI
%token TOK_USERNS %token TOK_USERNS
%token TOK_MQUEUE
%token TOK_DELETE
/* rlimits */ /* rlimits */
%token TOK_RLIMIT %token TOK_RLIMIT
@ -179,6 +181,7 @@ void add_local_entry(Profile *prof);
#include "ptrace.h" #include "ptrace.h"
#include "af_unix.h" #include "af_unix.h"
#include "userns.h" #include "userns.h"
#include "mqueue.h"
} }
%union { %union {
@ -196,6 +199,7 @@ void add_local_entry(Profile *prof);
ptrace_rule *ptrace_entry; ptrace_rule *ptrace_entry;
unix_rule *unix_entry; unix_rule *unix_entry;
userns_rule *userns_entry; userns_rule *userns_entry;
mqueue_rule *mqueue_entry;
flagvals flags; flagvals flags;
int fmode; int fmode;
@ -279,6 +283,10 @@ void add_local_entry(Profile *prof);
%type <fmode> userns_perms %type <fmode> userns_perms
%type <fmode> opt_userns_perm %type <fmode> opt_userns_perm
%type <userns_entry> userns_rule %type <userns_entry> userns_rule
%type <fmode> mqueue_perm
%type <fmode> mqueue_perms
%type <fmode> opt_mqueue_perm
%type <mqueue_entry> mqueue_rule
%% %%
@ -413,7 +421,7 @@ profile: opt_profile_flag profile_base
yyerror(_("Profile names must begin with a '/', namespace or keyword 'profile' or 'hat'.")); yyerror(_("Profile names must begin with a '/', namespace or keyword 'profile' or 'hat'."));
if ($1 == 2) if ($1 == 2)
prof->flags.hat = 1; prof->flags.flags |= FLAG_HAT;
$$ = prof; $$ = prof;
}; };
@ -440,7 +448,7 @@ hat: hat_start profile_base
if ($2->xattrs.list) if ($2->xattrs.list)
yyerror("hat profiles can't use xattrs matches"); yyerror("hat profiles can't use xattrs matches");
prof->flags.hat = 1; prof->flags.flags |= FLAG_HAT;
$$ = prof; $$ = prof;
}; };
@ -920,6 +928,22 @@ rules: rules opt_prefix capability
$$ = $1; $$ = $1;
}; };
rules: rules opt_prefix mqueue_rule
{
if ($2.owner)
yyerror(_("owner prefix not allowed on mqueue rules")); //is this true?
if ($2.deny && $2.audit) {
$3->deny = 1;
} else if ($2.deny) {
$3->deny = 1;
$3->audit = $3->mode;
} else if ($2.audit) {
$3->audit = $3->mode;
}
$1->rule_ents.push_back($3);
$$ = $1;
};
rules: rules hat rules: rules hat
{ {
PDEBUG("Matched: hat rule\n"); PDEBUG("Matched: hat rule\n");
@ -1591,6 +1615,62 @@ userns_rule: TOK_USERNS opt_userns_perm opt_conds TOK_END_OF_RULE
$$ = ent; $$ = ent;
} }
mqueue_perm: TOK_VALUE
{
if (strcmp($1, "create") == 0)
$$ = AA_MQUEUE_CREATE;
else if (strcmp($1, "open") == 0)
$$ = AA_MQUEUE_OPEN;
else if (strcmp($1, "delete") == 0)
$$ = AA_MQUEUE_DELETE;
else if (strcmp($1, "getattr") == 0)
$$ = AA_MQUEUE_GETATTR;
else if (strcmp($1, "setattr") == 0)
$$ = AA_MQUEUE_SETATTR;
else if (strcmp($1, "write") == 0)
$$ = AA_MQUEUE_WRITE;
else if (strcmp($1, "read") == 0)
$$ = AA_MQUEUE_READ;
else if ($1) {
parse_mqueue_mode($1, &$$, 1);
} else
$$ = 0;
if ($1)
free($1);
}
| TOK_CREATE { $$ = AA_MQUEUE_CREATE; }
| TOK_OPEN { $$ = AA_MQUEUE_OPEN; }
| TOK_DELETE { $$ = AA_MQUEUE_DELETE; }
| TOK_GETATTR { $$ = AA_MQUEUE_GETATTR; }
| TOK_SETATTR { $$ = AA_MQUEUE_SETATTR; }
| TOK_WRITE { $$ = AA_MQUEUE_WRITE; }
| TOK_READ { $$ = AA_MQUEUE_READ; }
| TOK_MODE
{
parse_mqueue_mode($1, &$$, 1);
free($1);
}
mqueue_perms: { /* nothing */ $$ = 0; }
| mqueue_perms mqueue_perm { $$ = $1 | $2; }
| mqueue_perms TOK_COMMA mqueue_perm { $$ = $1 | $3; }
opt_mqueue_perm: { /* nothing */ $$ = 0; }
| mqueue_perm { $$ = $1; }
| TOK_OPENPAREN mqueue_perms TOK_CLOSEPAREN { $$ = $2; }
mqueue_rule: TOK_MQUEUE opt_mqueue_perm opt_conds TOK_END_OF_RULE
{
mqueue_rule *ent = new mqueue_rule($2, $3);
$$ = ent;
}
| TOK_MQUEUE opt_mqueue_perm opt_conds TOK_ID TOK_END_OF_RULE
{
mqueue_rule *ent = new mqueue_rule($2, $3, $4);
$$ = ent;
}
hat_start: TOK_CARET {} hat_start: TOK_CARET {}
| TOK_HAT {} | TOK_HAT {}

View File

@ -34,6 +34,8 @@
#define AA_CLASS_SIGNAL 10 #define AA_CLASS_SIGNAL 10
#define AA_CLASS_NETV8 14 #define AA_CLASS_NETV8 14
#define AA_CLASS_LABEL 16 #define AA_CLASS_LABEL 16
#define AA_CLASS_POSIX_MQUEUE 17
#define AA_CLASS_SYSV_MQUEUE 18
#define AA_CLASS_NS 21 #define AA_CLASS_NS 21
/* defined in libapparmor's apparmor.h #define AA_CLASS_DBUS 32 */ /* defined in libapparmor's apparmor.h #define AA_CLASS_DBUS 32 */

View File

@ -110,9 +110,13 @@ static inline enum profile_mode str_to_mode(const char *str)
return MODE_UNSPECIFIED; return MODE_UNSPECIFIED;
}; };
#define FLAG_HAT 1
#define FLAG_DEBUG1 2
#define FLAG_DEBUG2 4
class flagvals { class flagvals {
public: public:
int hat; int flags;
enum profile_mode mode; enum profile_mode mode;
int audit; int audit;
int path; int path;
@ -124,7 +128,7 @@ public:
if (audit) if (audit)
os << ", Audit"; os << ", Audit";
if (hat) if (flags & FLAG_HAT)
os << ", Hat"; os << ", Hat";
os << "\n"; os << "\n";

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid label
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue label=,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid type
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue type=,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid queuename for type sysv
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue type=sysv /queuename,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid queuename for type posix
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue type=posix 1234,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid access name
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue invalidaccess /queuename,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid type option - posix
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue type=posixfoo,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid type option - sysv
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue type=sysvfoo,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid type option
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue type=foo,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid queuename - does not start with /
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue foo,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue invalid queuename - not only numbers
#=EXRESULT FAIL
#
/usr/bin/foo {
mqueue 1234foo,
}

View File

@ -0,0 +1,6 @@
#
#=DESCRIPTION mqueue rule outside of a profile
#=EXRESULT FAIL
#
mqueue,

View File

@ -0,0 +1,7 @@
#
#=DESCRIPTION mqueue generic rule
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue type option
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue type=posix,
mqueue type=sysv,
}

View File

@ -0,0 +1,7 @@
#
#=DESCRIPTION mqueue label option
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue label=bar,
}

View File

@ -0,0 +1,7 @@
#
#=DESCRIPTION mqueue valid sysv queue name
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue 1234,
}

View File

@ -0,0 +1,7 @@
#
#=DESCRIPTION mqueue valid posix queue name
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue /bar,
}

View File

@ -0,0 +1,7 @@
#
#=DESCRIPTION mqueue valid sysv queue name with type
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue type=sysv 1234,
}

View File

@ -0,0 +1,7 @@
#
#=DESCRIPTION mqueue valid posix queue name with type
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue type=posix /bar,
}

View File

@ -0,0 +1,7 @@
#
#=DESCRIPTION mqueue type and label defined
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue type=posix label=bar,
}

View File

@ -0,0 +1,7 @@
#
#=DESCRIPTION mqueue type, label and queue name defined
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue type=posix label=bar /baz,
}

View File

@ -0,0 +1,14 @@
#
#=DESCRIPTION mqueue valid access mode
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue create,
mqueue (create, getattr, setattr),
mqueue (open delete),
mqueue (read write),
mqueue r,
mqueue w,
mqueue rw,
mqueue wr,
}

View File

@ -0,0 +1,8 @@
#
#=DESCRIPTION mqueue full valid rule
#=EXRESULT PASS
#
/usr/bin/foo {
mqueue (create, write) type=posix label=baz /bar,
mqueue (open, delete) type=sysv label=baz 1234,
}

View File

@ -0,0 +1,10 @@
#
#=DESCRIPTION mqueue misc rules
#=EXRESULT PASS
#
/usr/bin/foo {
deny mqueue,
audit allow mqueue,
audit deny mqueue,
allow mqueue,
}

View File

@ -115,6 +115,8 @@ SRC=access.c \
openat.c \ openat.c \
pipe.c \ pipe.c \
pivot_root.c \ pivot_root.c \
posix_mq_rcv.c \
posix_mq_snd.c \
ptrace.c \ ptrace.c \
ptrace_helper.c \ ptrace_helper.c \
pwrite.c \ pwrite.c \
@ -135,6 +137,8 @@ SRC=access.c \
syscall_setdomainname.c \ syscall_setdomainname.c \
syscall_setscheduler.c \ syscall_setscheduler.c \
sysctl_proc.c \ sysctl_proc.c \
sysv_mq_rcv.c \
sysv_mq_snd.c \
tcp.c \ tcp.c \
transition.c \ transition.c \
unix_fd_client.c \ unix_fd_client.c \
@ -237,6 +241,7 @@ TESTS=aa_exec \
openat \ openat \
pipe \ pipe \
pivot_root \ pivot_root \
posix_ipc \
ptrace \ ptrace \
pwrite \ pwrite \
query_label \ query_label \
@ -250,6 +255,7 @@ TESTS=aa_exec \
setattr \ setattr \
symlink \ symlink \
syscall \ syscall \
sysv_ipc \
tcp \ tcp \
unix_fd_server \ unix_fd_server \
unix_socket_pathname \ unix_socket_pathname \
@ -312,6 +318,12 @@ dbus_service: dbus_message dbus_service.c dbus_common.o
dbus_unrequested_reply: dbus_service dbus_unrequested_reply.c dbus_common.o dbus_unrequested_reply: dbus_service dbus_unrequested_reply.c dbus_common.o
${CC} ${CFLAGS} ${LDFLAGS} $(filter-out dbus_service, $^) -o $@ ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1) ${CC} ${CFLAGS} ${LDFLAGS} $(filter-out dbus_service, $^) -o $@ ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1)
posix_mq_rcv: posix_mq_rcv.c
${CC} ${CFLAGS} ${LDFLAGS} $< -o $@ ${LDLIBS} -lrt
posix_mq_snd: posix_mq_snd.c
${CC} ${CFLAGS} ${LDFLAGS} $< -o $@ ${LDLIBS} -lrt
transition: transition.c transition: transition.c
${CC} ${CFLAGS} ${TRANSITION_CFLAGS} ${LDFLAGS} $< -o $@ ${LDLIBS} ${CC} ${CFLAGS} ${TRANSITION_CFLAGS} ${LDFLAGS} $< -o $@ ${LDLIBS}

View File

@ -423,6 +423,26 @@ sub gen_path($) {
} }
} }
sub gen_mqueue($) {
my $rule = shift;
my @rules = split (/:/, $rule);
if (@rules == 2) {
if ($rules[1] =~ /^ALL$/) {
push (@{$output_rules{$hat}}, " mqueue,\n");
} else {
push (@{$output_rules{$hat}}, " mqueue $rules[1],\n");
}
} elsif (@rules == 3) {
push (@{$output_rules{$hat}}, " mqueue $rules[1] $rules[2],\n");
} elsif (@rules == 4) {
push (@{$output_rules{$hat}}, " mqueue $rules[1] $rules[2] $rules[3],\n");
} elsif (@rules == 5) {
push (@{$output_rules{$hat}}, " mqueue $rules[1] $rules[2] $rules[3] $rules[4],\n");
} else {
(!$nowarn) && print STDERR "Warning: invalid mqueue description '$rule', ignored\n";
}
}
sub emit_flags($) { sub emit_flags($) {
my $hat = shift; my $hat = shift;
@ -492,6 +512,8 @@ sub gen_from_args() {
gen_xattr($rule); gen_xattr($rule);
} elsif ($rule =~ /^path:/) { } elsif ($rule =~ /^path:/) {
gen_path($rule); gen_path($rule);
} elsif ($rule =~ /^mqueue:/) {
gen_mqueue($rule);
} else { } else {
gen_file($rule, $qualifier); gen_file($rule, $qualifier);
} }

View File

@ -0,0 +1,12 @@
# Test semaphores first, as they could be used for synchronization
## TODO
# posix_sem.sh
## TODO
# posix_shm.sh
./posix_mq.sh

View File

@ -0,0 +1,31 @@
#ifndef POSIX_MQ_H_
#define POSIX_MQ_H_
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <semaphore.h>
#define QNAME "/testmq"
#define SHM_PATH "/unnamedsemtest"
#define SEM_PATH "/namedsemtest"
#define OBJ_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
#define BUF_SIZE 1024
struct shmbuf { // Buffer in shared memory
sem_t sem;
int cnt; // Number of bytes used in 'buf'
char buf[BUF_SIZE]; // Data being transferred
};
struct msgbuf {
long mtype;
char mtext[BUF_SIZE];
};
char *msg = "hello world";
#endif /* #ifndef POSIX_MQ_H_ */

View File

@ -0,0 +1,179 @@
#! /bin/bash
#Copyright (C) 2022 Canonical, Ltd.
#
#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License as
#published by the Free Software Foundation, version 2 of the
#License.
#=NAME posix_mq
#=DESCRIPTION
# This test verifies if mediation of posix message queues is working
#=END
pwd=`dirname $0`
pwd=`cd $pwd ; /bin/pwd`
bin=$pwd
. $bin/prologue.inc
requires_kernel_features ipc/posix_mqueue
requires_parser_support "mqueue,"
settest posix_mq_rcv
sender="$bin/posix_mq_snd"
receiver="$bin/posix_mq_rcv"
queuename="/queuename"
queuename2="/queuename2"
user="foo"
adduser --gecos "First Last,RoomNumber,WorkPhone,HomePhone" --no-create-home --disabled-password $user >/dev/null
echo "$user:password" | sudo chpasswd
userid=$(id -u $user)
# workaround to not have to set o+x
chmod 6755 $receiver
setcap cap_dac_read_search+pie $receiver
cleanup()
{
rm -f /dev/mqueue/$queuename
rm -f /dev/mqueue/$queuename2
deluser foo >/dev/null
}
do_onexit="cleanup"
do_test()
{
local desc="POSIX MQUEUE ($1)"
shift
runchecktest "$desc" "$@"
}
do_tests()
{
prefix=$1
expect_send=$2
expect_recv=$3
expect_open=$4
all_args=("$@")
rest_args=("${all_args[@]:5}")
do_test "$prefix" "$expect_send" $sender "$expect_recv" -c $sender -k $queuename "${rest_args[@]}"
# notify requires netlink permissions
do_test "$prefix : mq_notify" "$expect_send" $sender "$expect_recv" -c $sender -k $queuename -n mq_notify "${rest_args[@]}"
do_test "$prefix : select" "$expect_open" -c $sender -k $queuename -n select "${rest_args[@]}"
do_test "$prefix : poll" "$expect_open" -c $sender -k $queuename -n poll "${rest_args[@]}"
do_test "$prefix : epoll" "$expect_open" -c $sender -k $queuename -n epoll "${rest_args[@]}"
}
for username in "root" "$userid" ; do
if [ $username == "root" ] ; then
usercmd=""
else
usercmd="-u $userid"
fi
do_tests "unconfined $username" pass pass pass pass $usercmd
# No mqueue perms
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "$sender:px" -- image=$sender
do_tests "confined $username - no perms" fail fail fail fail $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "deny:mqueue" "$sender:px" -- image=$sender "deny mqueue"
do_tests "confined $username - deny perms" fail fail fail fail $usercmd
# generic mqueue
# 2 Potential failures caused by missing other x permission in path
# to tests. Usually on the user home dir as it is now default to
# create a user without that
# * if you seen a capability dac_read_search denied failure from
# apparmor when doing "root" username tests
# * if doing the $userid set of tests and you see
# Permission denied in the test output
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue" "$sender:px" -- image=$sender "mqueue"
do_tests "confined $username - mqueue" pass pass pass pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:type=posix" "$sender:px" -- image=$sender "mqueue:type=posix"
do_tests "confined $username - mqueue type=posix" pass pass pass pass $usercmd
# queue name
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:$queuename" "$sender:px" -- image=$sender "mqueue:$queuename"
do_tests "confined $username - mqueue /name 1" pass pass pass pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue" "$sender:px" -- image=$sender "mqueue:$queuename"
do_tests "confined $username - mqueue /name 2" pass pass pass pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:$queuename" "$sender:px" -- image=$sender "mqueue"
do_tests "confined $username - mqueue /name 3" pass pass pass pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:$queuename" "$sender:px" -- image=$sender "mqueue:$queuename2"
do_tests "confined $username - mqueue /name 4" fail fail fail fail $usercmd -t 1
# specific permissions
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:(create,read,delete,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:write"
do_tests "confined $username - specific 1" pass pass pass pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:(read,delete,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:write"
do_tests "confined $username - specific 2" fail fail fail fail $usercmd -t 1
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:(create,delete,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:write"
do_tests "confined $username - specific 3" fail fail fail fail $usercmd -t 1
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:(create,read,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:write"
do_tests "confined $username - specific 4" fail fail fail fail $usercmd -t 1
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:(create,read,delete,setattr)" "$sender:px" -- image=$sender "mqueue:write"
do_tests "confined $username - specific 5" pass pass pass pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:(create,read,delete,getattr)" "$sender:px" -- image=$sender "mqueue:write"
do_tests "confined $username - specific 6" pass pass pass pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:(create,read,delete,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:read"
do_tests "confined $username - specific 7" fail fail fail fail $usercmd -t 1
# unconfined receiver
genprofile image=$sender "mqueue"
do_tests "confined sender $username - unconfined receiver" pass pass pass pass $usercmd
# unconfined sender
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue" "$sender:ux"
do_tests "confined receiver $username - unconfined sender" pass pass pass pass $usercmd
# queue label
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:label=$receiver" "$sender:px" -- image=$sender "mqueue:label=$receiver"
do_tests "confined $username - mqueue label 1" xpass xpass xpass xpass $usercmd
# queue name and label
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "network:netlink" "mqueue:(create,read,delete):type=posix:label=$receiver:$queuename" "$sender:px" -- image=$sender "mqueue:(open,write):type=posix:label=$receiver:$queuename"
do_tests "confined $username - mqueue label 2" xpass xpass xpass xpass $usercmd
# ensure we are cleaned up for next pass
removeprofile
rm -f /dev/mqueue/$queuename
rm -f /dev/mqueue/$queuename2
done
# cross user tests
# confined root with cap ??override
# confined root without cap ??override

View File

@ -0,0 +1,306 @@
#include <mqueue.h>
#include <stdlib.h>
#include <signal.h>
#include <poll.h>
#include <sys/epoll.h>
#include <time.h>
#include "posix_mq.h"
int timeout = 5; //seconds
char *queuename = QNAME;
enum notify_options {
DO_NOT_NOTIFY,
MQ_NOTIFY,
SELECT,
POLL,
EPOLL
};
int receive_message(mqd_t mqd, char needs_timeout) {
ssize_t nbytes;
struct mq_attr attr;
char *buf = NULL;
if (mq_getattr(mqd, &attr) == -1) {
perror("FAIL - could not mq_getattr");
goto out;
}
buf = malloc(attr.mq_msgsize);
if (buf == NULL) {
perror("FAIL - could not malloc");
goto out;
}
if (needs_timeout) { /* do we need this or should we just use mq_timedreceive always? */
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout;
nbytes = mq_timedreceive(mqd, buf, attr.mq_msgsize,
NULL, &ts);
} else {
attr.mq_flags |= O_NONBLOCK;
if (mq_setattr(mqd, &attr, NULL) == -1){
perror("FAIL - could not mq_setattr");
goto out;
}
nbytes = mq_receive(mqd, buf, attr.mq_msgsize, NULL);
}
if (nbytes < 0) {
perror("FAIL - could not receive msg");
goto out;
}
buf[nbytes] = 0;
if (strncmp(buf, msg, BUF_SIZE) != 0) {
fprintf(stderr, "FAIL - msg received does not match: %s - %s\n", buf, msg);
goto out;
}
printf("PASS\n");
out:
free(buf);
if (mq_close(mqd) == (mqd_t) -1) {
perror("FAIL - could not close mq");
exit(EXIT_FAILURE);
}
if (mq_unlink(queuename) == (mqd_t) -1) {
perror("FAIL - could unlink mq");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
static void handle_signal(union sigval sv) {
mqd_t mqd = *((mqd_t *) sv.sival_ptr);
receive_message(mqd, 0);
}
static void usage(char *prog_name, char *msg)
{
if (msg != NULL)
fprintf(stderr, "%s\n", msg);
fprintf(stderr, "Usage: %s [options]\n", prog_name);
fprintf(stderr, "Options are:\n");
fprintf(stderr, "-n get notified if there's an item in the queue\n");
fprintf(stderr, " available options are: mq_notify, select, poll and epoll\n");
fprintf(stderr, "-k message queue name (default is %s)\n", QNAME);
fprintf(stderr, "-c path of the client binary\n");
fprintf(stderr, "-u run test as specified UID\n");
fprintf(stderr, "-t timeout in seconds\n");
exit(EXIT_FAILURE);
}
void receive_mq_notify(mqd_t mqd)
{
struct sigevent sev;
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = handle_signal;
sev.sigev_notify_attributes = NULL;
sev.sigev_value.sival_ptr = &mqd;
if (mq_notify(mqd, &sev) == -1) {
perror(" FAIL - could not mq_notify");
exit(EXIT_FAILURE);
}
sleep(timeout);
fprintf(stderr, "FAIL - could not mq_notify: Connection timed out\n");
}
void receive_select(mqd_t mqd)
{
fd_set read_fds;
struct timeval tv;
tv.tv_sec = timeout;
tv.tv_usec = 0;
FD_ZERO(&read_fds);
FD_SET(mqd, &read_fds);
if (select(mqd + 1, &read_fds, NULL, NULL, &tv) == -1) {
perror("FAIL - could not select");
exit(EXIT_FAILURE);
} else {
if (FD_ISSET(mqd, &read_fds))
receive_message(mqd, 0);
}
}
void receive_poll(mqd_t mqd)
{
struct pollfd fds[1];
fds[0].fd = mqd;
fds[0].events = POLLIN;
if (poll(fds, 1, timeout * 1000) == -1) {
perror("FAIL - could not poll");
exit(EXIT_FAILURE);
} else {
if (fds[0].revents & POLLIN)
receive_message(mqd, 0);
}
}
void receive_epoll(mqd_t mqd)
{
int epfd = epoll_create(1);
if (epfd == -1) {
perror("FAIL - could not create epoll");
exit(EXIT_FAILURE);
}
struct epoll_event ev, rev[1];
ev.events = EPOLLIN;
ev.data.fd = mqd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, mqd, &ev) == -1) {
perror("FAIL - could not add mqd to epoll");
exit(EXIT_FAILURE);
}
if (epoll_wait(epfd, rev, 1, timeout * 1000) == -1) {
perror("FAIL - could not epoll_wait");
exit(EXIT_FAILURE);
} else {
if (rev[0].data.fd == mqd && rev[0].events & EPOLLIN)
receive_message(mqd, 0);
}
}
void receive(enum notify_options notify, mqd_t mqd)
{
switch(notify) {
case DO_NOT_NOTIFY:
receive_message(mqd, 1);
return;
case MQ_NOTIFY:
receive_mq_notify(mqd);
break;
case SELECT:
receive_select(mqd);
break;
case POLL:
receive_poll(mqd);
break;
case EPOLL:
receive_epoll(mqd);
break;
}
}
int main(int argc, char *argv[])
{
char opt = 0;
enum notify_options notify = DO_NOT_NOTIFY;
mqd_t mqd;
char *client = NULL;
int uid;
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = BUF_SIZE;
attr.mq_curmsgs = 0;
while ((opt = getopt(argc, argv, "n:k:c:u:t:")) != -1) {
switch (opt) {
case 'n':
if (strcmp(optarg, "mq_notify") == 0)
notify = MQ_NOTIFY;
else if (strcmp(optarg, "select") == 0)
notify = SELECT;
else if (strcmp(optarg, "poll") == 0)
notify = POLL;
else if (strcmp(optarg, "epoll") == 0)
notify = EPOLL;
else
usage(argv[0], "invalid option for -n");
break;
case 'k':
queuename = optarg;
if (queuename == NULL)
usage(argv[0], "-k option must specify the queue name\n");
break;
case 'c':
client = optarg;
if (client == NULL)
usage(argv[0], "-c option must specify the client binary\n");
break;
case 'u':
/* change file mode on output before setuid drops
* privs. This is required to make sure we can
* write to the output file and in some cases
* even exec with our inherited output file
*
* This assume test infrastructure creates the
* file as root and dups stderr to stdout
*/
if (fchmod(fileno(stdout), 0666) == -1) {
perror("FAIL - could not set output file mode");
exit(EXIT_FAILURE);
}
if (fchmod(fileno(stderr), 0666) == -1) {
perror("FAIL - could not set output file mode");
exit(EXIT_FAILURE);
}
uid = atoi(optarg);
if (setuid(uid) < 0) {
perror("FAIL - could not setuid");
exit(EXIT_FAILURE);
}
break;
case 't':
timeout = atoi(optarg);
break;
default:
usage(argv[0], "Unrecognized option\n");
}
}
mqd = mq_open(queuename, O_CREAT | O_RDONLY, OBJ_PERMS, &attr);
if (mqd == (mqd_t) -1) {
perror("FAIL - could not open mq");
exit(EXIT_FAILURE);
}
/* exec the client */
int pid = fork();
if (pid == -1) {
perror("FAIL - could not fork");
exit(EXIT_FAILURE);
} else if (!pid) {
if (client == NULL) {
usage(argv[0], "client not specified");
exit(EXIT_FAILURE);
/* execution of the main thread continues
* in case the client will be manually executed
*/
}
execl(client, client, queuename, NULL);
printf("FAIL %d - execlp %s %s- %m\n", getuid(), client, queuename);
exit(EXIT_FAILURE);
}
receive(notify, mqd);
/* when the notification fails because of timeout, it ends up here
* so, clean up the mqueue
*/
if (mq_close(mqd) == (mqd_t) -1) {
perror("FAIL - could not close mq");
exit(EXIT_FAILURE);
}
if (mq_unlink(queuename) == (mqd_t) -1) {
perror("FAIL - could unlink mq");
exit(EXIT_FAILURE);
}
return 0;
}

View File

@ -0,0 +1,32 @@
#include <mqueue.h>
#include <stdlib.h>
#include "posix_mq.h"
int main(int argc, char * argv[])
{
mqd_t mqd;
char *queuename = QNAME;
if (argc > 1) {
queuename = argv[1];
}
mqd = mq_open(queuename, O_WRONLY);
if (mqd == (mqd_t) -1) {
perror("FAIL sender - could not open mq");
return 1;
}
if (mq_send(mqd, msg, strnlen(msg, BUF_SIZE), 0) == -1) {
perror("FAIL sender - could not send");
return 1;
}
if (mq_close(mqd) == (mqd_t) -1) {
perror("FAIL sender - could not close mq");
return 1;
}
//printf("PASS client\n");
return 0;
}

View File

@ -0,0 +1,9 @@
# Test semaphores first, as they could be used for synchronization
## TODO
# sysv_sem.sh
## TODO
# sysv_shm.sh
./sysv_mq.sh

View File

@ -0,0 +1,35 @@
#ifndef SYSV_MQ_H_
#define SYSV_MQ_H_
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <sys/stat.h>
#define MQ_KEY (123)
#define MQ_TYPE (0)
#define SHM_KEY (456)
#define SEM_KEY (789)
#define OBJ_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
#define BUF_SIZE 1024
struct msg_buf {
long mtype;
char mtext[BUF_SIZE];
};
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
char *msg = "hello world";
#endif /* #ifndef SYSV_MQ_H_ */

View File

@ -0,0 +1,171 @@
#! /bin/bash
#Copyright (C) 2022 Canonical, Ltd.
#
#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License as
#published by the Free Software Foundation, version 2 of the
#License.
#=NAME sysv_mq
#=DESCRIPTION
# This test verifies if mediation of sysv message queues is working
#=END
pwd=`dirname $0`
pwd=`cd $pwd ; /bin/pwd`
bin=$pwd
. $bin/prologue.inc
requires_kernel_features ipc/sysv_mqueue
requires_parser_support "mqueue,"
settest sysv_mq_rcv
sender="$bin/sysv_mq_snd"
receiver="$bin/sysv_mq_rcv"
qkey=123
qkey2=124
semaphore=456
user="foo"
adduser --gecos "First Last,RoomNumber,WorkPhone,HomePhone" --no-create-home --disabled-password $user >/dev/null
echo "$user:password" | sudo chpasswd
userid=$(id -u $user)
# workaround to not have to set o+x
chmod 6755 $receiver
setcap cap_dac_read_search+pie $receiver
cleanup()
{
ipcrm --queue-key $qkey >/dev/null 2>&1
ipcrm --queue-key $qkey2 >/dev/null 2>&1
ipcrm --semaphore-key $semaphore >/dev/null 2>&1
deluser foo >/dev/null
}
do_onexit="cleanup"
do_test()
{
local desc="SYSV MQUEUE ($1)"
shift
runchecktest "$desc" "$@"
}
do_tests()
{
prefix=$1
expect_send=$2
all_args=("$@")
rest_args=("${all_args[@]:2}")
do_test "$prefix" "$expect_send" -c $sender -k $qkey -s $semaphore "${rest_args[@]}"
}
for username in "root" "$userid" ; do
if [ $username == "root" ] ; then
usercmd=""
else
usercmd="-u $userid"
fi
do_tests "unconfined $username" pass $usercmd
# No mqueue perms
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "$sender:px" -- image=$sender
do_tests "confined $username - no perms" fail $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "deny:mqueue" "$sender:px" -- image=$sender "deny mqueue"
do_tests "confined $username - deny perms" fail $usercmd
# generic mqueue
# 2 Potential failures caused by missing other x permission in path
# to tests. Usually on the user home dir as it is now default to
# create a user without that
# * if you seen a capability dac_read_search denied failure from
# apparmor when doing "root" username tests
# * if doing the $userid set of tests and you see
# Permission denied in the test output
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue" "$sender:px" -- image=$sender "mqueue"
do_tests "confined $username - mqueue" pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:type=sysv" "$sender:px" -- image=$sender "mqueue:type=sysv"
do_tests "confined $username - mqueue type=sysv" pass $usercmd
# queue name
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:$qkey" "$sender:px" -- image=$sender "mqueue:$qkey"
do_tests "confined $username - mqueue /name 1" pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue" "$sender:px" -- image=$sender "mqueue:$qkey"
do_tests "confined $username - mqueue /name 2" pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:$qkey" "$sender:px" -- image=$sender "mqueue"
do_tests "confined $username - mqueue /name 3" pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:$qkey" "$sender:px" -- image=$sender "mqueue:$qkey2"
do_tests "confined $username - mqueue /name 4" fail $usercmd -t 1
# specific permissions
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:(create,read,delete,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:(open,write)"
do_tests "confined $username - specific 1" pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:(read,delete,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:(open,write)"
do_tests "confined $username - specific 2" fail $usercmd -t 1
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:(create,delete,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:(open,write)"
do_tests "confined $username - specific 3" fail $usercmd -t 1
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:(create,read,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:(open,write)"
do_tests "confined $username - specific 4" fail $usercmd -t 1
# we need to remove queue since the previous test didn't
ipcrm --queue-key $qkey >/dev/null 2>&1
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:(create,read,delete,setattr)" "$sender:px" -- image=$sender "mqueue:(open,write)"
do_tests "confined $username - specific 5" pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:(create,read,delete,getattr)" "$sender:px" -- image=$sender "mqueue:(open,write)"
do_tests "confined $username - specific 6" pass $usercmd
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:(create,read,delete,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:(open,read)"
do_tests "confined $username - specific 7" fail $usercmd -t 1
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:(create,read,delete,getattr,setattr)" "$sender:px" -- image=$sender "mqueue:write"
do_tests "confined $username - specific 7" fail $usercmd -t 1
# unconfined receiver
genprofile image=$sender "mqueue"
do_tests "confined sender $username - unconfined receiver" pass $usercmd
# unconfined sender
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue" "$sender:ux"
do_tests "confined receiver $username - unconfined sender" pass $usercmd
# queue label
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:label=$receiver" "$sender:px" -- image=$sender "mqueue:label=$receiver"
do_tests "confined $username - mqueue label 1" xpass $usercmd
# queue name and label
genprofile "cap:sys_resource:deny" "cap:setuid" "cap:fowner" "mqueue:(create,read,delete):type=sysv:label=$receiver:$qkey" "$sender:px" -- image=$sender "mqueue:(open,write):type=sysv:label=$receiver:$qkey"
do_tests "confined $username - mqueue label 2" xpass $usercmd
# ensure we are cleaned up for next pass
removeprofile
done
# confined root with cap ??override
# confined root without cap ??override
# deliver message by mtype (posix lacks this)

View File

@ -0,0 +1,187 @@
#define _GNU_SOURCE
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "sysv_mq.h"
int timeout = 5; //seconds
key_t mqkey = MQ_KEY;
long mqtype = MQ_TYPE;
key_t semkey = SEM_KEY;
// missing getattr and setattr
int receive_message(int qid, long qtype)
{
struct msg_buf mb;
ssize_t nbytes;
nbytes = msgrcv(qid, &mb, sizeof(mb.mtext), qtype,
MSG_NOERROR | IPC_NOWAIT);
if (nbytes < 0) {
perror("FAIL - could not receive msg");
return EXIT_FAILURE;
}
mb.mtext[nbytes] = 0;
if (strncmp(mb.mtext, msg, BUF_SIZE) != 0) {
fprintf(stderr, "FAIL - msg received does not match: %s - %s\n", mb.mtext, msg);
return EXIT_FAILURE;
}
printf("PASS\n");
return EXIT_SUCCESS;
}
int receive(int qid, long qtype, int semid)
{
struct sembuf sop;
sop.sem_num = 0;
sop.sem_op = 0;
sop.sem_flg = 0;
struct timespec ts;
ts.tv_sec = timeout;
ts.tv_nsec = 0;
if (semtimedop(semid, &sop, 1, &ts) < 0) {
perror("FAIL - could not wait for semaphore");
return EXIT_FAILURE;
}
return receive_message(qid, qtype);
}
static void usage(char *prog_name, char *msg)
{
if (msg != NULL)
fprintf(stderr, "%s\n", msg);
fprintf(stderr, "Usage: %s [options]\n", prog_name);
fprintf(stderr, "Options are:\n");
fprintf(stderr, "-k message queue key (default is %d)\n", MQ_KEY);
fprintf(stderr, "-e message queue type (default is %d)\n", MQ_TYPE);
fprintf(stderr, "-c path of the client binary\n");
fprintf(stderr, "-u run test as specified UID\n");
fprintf(stderr, "-t timeout in seconds\n");
fprintf(stderr, "-s semaphore key (default is %d)\n", SEM_KEY);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
char opt = 0;
char *client = NULL;
int uid;
int qid;
int semid;
int rc = EXIT_SUCCESS;
const int stringsize = 50;
char smqkey[stringsize];
char ssemkey[stringsize];
while ((opt = getopt(argc, argv, "k:c:u:t:e:s:")) != -1) {
switch (opt) {
case 'k':
mqkey = atoi(optarg);
break;
case 'c':
client = optarg;
if (client == NULL)
usage(argv[0], "-c option must specify the client binary\n");
break;
case 'u':
/* change file mode on output before setuid drops
* privs. This is required to make sure we can
* write to the output file and in some cases
* even exec with our inherited output file
*
* This assume test infrastructure creates the
* file as root and dups stderr to stdout
*/
if (fchmod(fileno(stdout), 0666) == -1) {
perror("FAIL - could not set output file mode");
exit(EXIT_FAILURE);
}
if (fchmod(fileno(stderr), 0666) == -1) {
perror("FAIL - could not set output file mode");
exit(EXIT_FAILURE);
}
uid = atoi(optarg);
if (setuid(uid) < 0) {
perror("FAIL - could not setuid");
exit(EXIT_FAILURE);
}
break;
case 't':
timeout = atoi(optarg);
break;
case 'e':
mqtype = atoi(optarg);
break;
case 's':
semkey = atoi(optarg);
break;
default:
usage(argv[0], "Unrecognized option\n");
}
}
qid = msgget(mqkey, IPC_CREAT | OBJ_PERMS);
if (qid == -1) {
perror("FAIL - could not msgget");
return EXIT_FAILURE;
}
semid = semget(semkey, 1, IPC_CREAT | OBJ_PERMS);
if (semid == -1) {
perror("FAIL - could not get semaphore");
rc = EXIT_FAILURE;
goto out_mq;
}
union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("FAIL - could not get semaphore");
rc = EXIT_FAILURE;
goto out;
}
/* exec the client */
int pid = fork();
if (pid == -1) {
perror("FAIL - could not fork");
rc = EXIT_FAILURE;
goto out;
} else if (!pid) {
if (client == NULL) {
usage(argv[0], "client not specified");
exit(EXIT_FAILURE);
/* execution of the main thread continues
* in case the client will be manually executed
*/
}
snprintf(smqkey, stringsize - 1, "%d", mqkey);
snprintf(ssemkey, stringsize - 1, "%d", semkey);
execl(client, client, smqkey, ssemkey, NULL);
printf("FAIL %d - execl %s %d - %m\n", getuid(), client, mqkey);
exit(EXIT_FAILURE);
}
rc = receive(qid, mqtype, semid);
out:
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("FAIL - could not remove semaphore");
rc = EXIT_FAILURE;
}
out_mq:
if (msgctl(qid, IPC_RMID, NULL) < 0) {
perror("FAIL - could not remove msg queue");
rc = EXIT_FAILURE;
}
return rc;
}

View File

@ -0,0 +1,55 @@
#include "sysv_mq.h"
int main(int argc, char *argv[])
{
key_t mqkey = MQ_KEY;
key_t semkey = SEM_KEY;
long qtype = 1;
int qid, semid;
struct msg_buf mb;
if (argc != 1 && argc != 3) {
fprintf(stderr, "FAIL sender - specify values for message queue"
" key and semaphore key, respectively \n");
return EXIT_FAILURE;
}
if (argc > 1) {
mqkey = atoi(argv[1]);
semkey = atoi(argv[2]);
}
qid = msgget(mqkey, IPC_CREAT | OBJ_PERMS);
if (qid == -1) {
perror("FAIL sender - could not msgget");
exit(EXIT_FAILURE);
}
semid = semget(semkey, 1, IPC_CREAT | OBJ_PERMS);
if (semid == -1) {
perror("FAIL sender - could not get semaphore");
exit(EXIT_FAILURE);
}
snprintf(mb.mtext, sizeof(mb.mtext), "%s", msg);
mb.mtype = qtype;
if (msgsnd(qid, &mb, sizeof(struct msg_buf),
IPC_NOWAIT) == -1) {
perror("FAIL sender - could not msgsnd");
exit(EXIT_FAILURE);
}
/* notify using semaphore */
struct sembuf sop;
sop.sem_num = 0;
sop.sem_op = -1;
sop.sem_flg = 0;
if (semop(semid, &sop, 1) == -1) {
perror("FAIL sender - could not notify using semaphore");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}

View File

@ -51,6 +51,7 @@ from apparmor.rule.network import NetworkRule
from apparmor.rule.ptrace import PtraceRule from apparmor.rule.ptrace import PtraceRule
from apparmor.rule.signal import SignalRule from apparmor.rule.signal import SignalRule
from apparmor.rule.userns import UserNamespaceRule from apparmor.rule.userns import UserNamespaceRule
from apparmor.rule.mqueue import MessageQueueRule
from apparmor.translations import init_translation from apparmor.translations import init_translation
_ = init_translation() _ = init_translation()
@ -1728,6 +1729,14 @@ def collapse_log(hashlog, ignore_null_profiles=True):
if not hat_exists or not is_known_rule(aa[profile][hat], 'userns', userns_event): if not hat_exists or not is_known_rule(aa[profile][hat], 'userns', userns_event):
log_dict[aamode][full_profile]['userns'].add(userns_event) log_dict[aamode][full_profile]['userns'].add(userns_event)
mqueue = hashlog[aamode][full_profile]['mqueue']
for access in mqueue.keys():
for mqueue_type in mqueue[access]:
for mqueue_name in mqueue[access][mqueue_type]:
mqueue_event = MessageQueueRule(access, mqueue_type, MessageQueueRule.ALL, mqueue_name, log_event=True)
if not hat_exists or not is_known_rule(aa[profile][hat], 'mqueue', mqueue_event):
log_dict[aamode][full_profile]['mqueue'].add(mqueue_event)
return log_dict return log_dict
@ -2104,6 +2113,7 @@ def match_line_against_rule_classes(line, profile, file, lineno, in_preamble):
'rlimit', 'rlimit',
'signal', 'signal',
'userns', 'userns',
'mqueue',
): ):
if rule_name in ruletypes: if rule_name in ruletypes:

View File

@ -58,6 +58,7 @@ class ReadLog:
'ptrace': hasher(), 'ptrace': hasher(),
'signal': hasher(), 'signal': hasher(),
'userns': hasher(), 'userns': hasher(),
'mqueue': hasher(),
} }
def prefetch_next_log_entry(self): def prefetch_next_log_entry(self):
@ -188,7 +189,12 @@ class ReadLog:
elif e['class'] and e['class'] == 'namespace': elif e['class'] and e['class'] == 'namespace':
if e['denied_mask'].startswith('userns'): if e['denied_mask'].startswith('userns'):
self.hashlog[aamode][full_profile]['userns'][e['denied_mask'].removeprefix('userns_')] = True self.hashlog[aamode][full_profile]['userns'][e['denied_mask'].removeprefix('userns_')] = True
return None return
elif e['class'] and e['class'].endswith('mqueue'):
mqueue_type = e['class'].partition('_')[0]
self.hashlog[aamode][full_profile]['mqueue'][e['denied_mask']][mqueue_type][e['name']] = True
return
elif self.op_type(e) == 'file': elif self.op_type(e) == 'file':
# Map c (create) and d (delete) to w (logging is more detailed than the profile language) # Map c (create) and d (delete) to w (logging is more detailed than the profile language)

View File

@ -28,6 +28,7 @@ from apparmor.rule.ptrace import PtraceRule, PtraceRuleset
from apparmor.rule.rlimit import RlimitRule, RlimitRuleset from apparmor.rule.rlimit import RlimitRule, RlimitRuleset
from apparmor.rule.signal import SignalRule, SignalRuleset from apparmor.rule.signal import SignalRule, SignalRuleset
from apparmor.rule.userns import UserNamespaceRule, UserNamespaceRuleset from apparmor.rule.userns import UserNamespaceRule, UserNamespaceRuleset
from apparmor.rule.mqueue import MessageQueueRule, MessageQueueRuleset
from apparmor.translations import init_translation from apparmor.translations import init_translation
_ = init_translation() _ = init_translation()
@ -44,6 +45,7 @@ ruletypes = {
'rlimit': {'rule': RlimitRule, 'ruleset': RlimitRuleset}, 'rlimit': {'rule': RlimitRule, 'ruleset': RlimitRuleset},
'signal': {'rule': SignalRule, 'ruleset': SignalRuleset}, 'signal': {'rule': SignalRule, 'ruleset': SignalRuleset},
'userns': {'rule': UserNamespaceRule, 'ruleset': UserNamespaceRuleset}, 'userns': {'rule': UserNamespaceRule, 'ruleset': UserNamespaceRuleset},
'mqueue': {'rule': MessageQueueRule, 'ruleset': MessageQueueRuleset},
} }
@ -188,6 +190,7 @@ class ProfileStorage:
'network', 'network',
'dbus', 'dbus',
'mount', 'mount',
'mqueue',
'signal', 'signal',
'ptrace', 'ptrace',
'pivot_root', 'pivot_root',

View File

@ -52,6 +52,7 @@ RE_PROFILE_PTRACE = re.compile(RE_AUDIT_DENY + r'(ptrace\s*,|ptrace(?P<details>\
RE_PROFILE_PIVOT_ROOT = re.compile(RE_AUDIT_DENY + r'(pivot_root\s*,|pivot_root\s+[^#]*\s*,)' + RE_EOL) RE_PROFILE_PIVOT_ROOT = re.compile(RE_AUDIT_DENY + r'(pivot_root\s*,|pivot_root\s+[^#]*\s*,)' + RE_EOL)
RE_PROFILE_UNIX = re.compile(RE_AUDIT_DENY + r'(unix\s*,|unix\s+[^#]*\s*,)' + RE_EOL) RE_PROFILE_UNIX = re.compile(RE_AUDIT_DENY + r'(unix\s*,|unix\s+[^#]*\s*,)' + RE_EOL)
RE_PROFILE_USERNS = re.compile(RE_AUDIT_DENY + r'(userns\s*,|userns(?P<details>\s+[^#]*)\s*,)' + RE_EOL) RE_PROFILE_USERNS = re.compile(RE_AUDIT_DENY + r'(userns\s*,|userns(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
RE_PROFILE_MQUEUE = re.compile(RE_AUDIT_DENY + r'(mqueue\s*,|mqueue(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
# match anything that's not " or #, or matching quotes with anything except quotes inside # match anything that's not " or #, or matching quotes with anything except quotes inside
__re_no_or_quoted_hash = '([^#"]|"[^"]*")*' __re_no_or_quoted_hash = '([^#"]|"[^"]*")*'

View File

@ -0,0 +1,230 @@
# ----------------------------------------------------------------------
# Copyright (C) 2022 Canonical, Ltd.
#
# 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 as 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.
#
# ----------------------------------------------------------------------
import re
from apparmor.regex import RE_PROFILE_MQUEUE, RE_PROFILE_NAME
from apparmor.common import AppArmorBug, AppArmorException
from apparmor.rule import BaseRule, BaseRuleset, check_and_split_list, logprof_value_or_all, parse_modifiers, quote_if_needed
# setup module translations
from apparmor.translations import init_translation
_ = init_translation()
access_keywords_read = ['r', 'read']
access_keywords_write = ['w', 'write']
access_keywords_rw = ['rw', 'wr']
access_keywords_other = ['create', 'open', 'delete', 'getattr', 'setattr']
access_keywords = access_keywords_read + access_keywords_write + access_keywords_rw + access_keywords_other
joint_access_keyword = r'\s*(' + '|'.join(access_keywords) + r')\s*'
RE_ACCESS_KEYWORDS = (joint_access_keyword + # one of the access_keyword or
'|' + # or
r'\(' + joint_access_keyword + '(' + r'(\s|,)+' + joint_access_keyword + ')*' + r'\)' # one or more access_keyword in (...)
)
RE_MQUEUE_NAME = r'(?P<%s>(/\S+|\d*))' # / + string for posix, or digits for sys
RE_MQUEUE_TYPE = r'(?P<%s>(sysv|posix))' # type can be sysv or posix
RE_MQUEUE_DETAILS = re.compile(
'^' +
r'(\s+(?P<access>' + RE_ACCESS_KEYWORDS + '))?' + # optional access keyword(s)
r'(\s+(type=' + RE_MQUEUE_TYPE % 'mqueue_type' + '))?' + # optional type
r'(\s+(label=' + RE_PROFILE_NAME % 'label' + '))?' + # optional label
r'(\s+(' + RE_MQUEUE_NAME % 'mqueue_name' + '))?' + # optional mqueue name
r'\s*$')
class MessageQueueRule(BaseRule):
'''Class to handle and store a single mqueue rule'''
# Nothing external should reference this class, all external users
# should reference the class field MessageQueueRule.ALL
class __MessageQueueAll(object):
pass
ALL = __MessageQueueAll
rule_name = 'mqueue'
_match_re = RE_PROFILE_MQUEUE
def __init__(self, access, mqueue_type, label, mqueue_name,
audit=False, deny=False, allow_keyword=False,
comment='', log_event=None):
super().__init__(audit=audit, deny=deny,
allow_keyword=allow_keyword,
comment=comment,
log_event=log_event)
self.access, self.all_access, unknown_items = check_and_split_list(access, access_keywords, self.ALL, type(self).__name__, 'access')
if unknown_items:
raise AppArmorException(_('Passed unknown access keyword to %s: %s') % (type(self).__name__, ' '.join(unknown_items)))
self.label, self.all_labels = self._aare_or_all(label, 'label', is_path=False, log_event=log_event)
self.mqueue_type, self.all_mqueue_types = self._aare_or_all(mqueue_type, 'type', is_path=False, log_event=log_event)
self.mqueue_name, self.all_mqueue_names = self._aare_or_all(mqueue_name, 'mqueue_name', is_path=False, log_event=log_event)
self.validate_mqueue_name()
def validate_mqueue_name(self):
# The regex checks if it starts with / or if it's numeric
if self.all_mqueue_types or self.all_mqueue_names:
return
if self.mqueue_type.regex == 'sysv' and not self.mqueue_name.regex.isnumeric():
raise AppArmorException(_('Queue name for SYSV must be a positive integer'))
elif self.mqueue_type.regex == 'posix' and not self.mqueue_name.regex.startswith('/'):
raise AppArmorException(_('Queue name for POSIX must begin with /'))
@classmethod
def _create_instance(cls, raw_rule, matches):
'''parse raw_rule and return instance of this class'''
audit, deny, allow_keyword, comment = parse_modifiers(matches)
rule_details = ''
if matches.group('details'):
rule_details = matches.group('details')
if rule_details:
details = RE_MQUEUE_DETAILS.search(rule_details)
if not details:
raise AppArmorException(_("Invalid or unknown keywords in 'mqueue %s" % rule_details))
if details.group('access'):
access = details.group('access')
if access.startswith('(') and access.endswith(')'):
access = access[1:-1]
access = access.replace(',', ' ').split() # split by ',' or whitespace
else:
access = cls.ALL
if details.group('mqueue_type'):
mqueue_type = details.group('mqueue_type')
else:
mqueue_type = cls.ALL
if details.group('label'):
label = details.group('label')
else:
label = cls.ALL
if details.group('mqueue_name'):
mqueue_name = details.group('mqueue_name')
else:
mqueue_name = cls.ALL
else:
access = cls.ALL
mqueue_type = cls.ALL
label = cls.ALL
mqueue_name = cls.ALL
return cls(access, mqueue_type, label, mqueue_name,
audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment)
def get_clean(self, depth=0):
'''return rule (in clean/default formatting)'''
space = ' ' * depth
if self.all_access:
access = ''
elif len(self.access) == 1:
access = ' %s' % ' '.join(self.access)
elif self.access:
access = ' (%s)' % ' '.join(sorted(self.access))
else:
raise AppArmorBug('Empty access in mqueue rule')
if self.all_mqueue_types:
mqueue_type = ''
elif self.mqueue_type:
mqueue_type = ' type=%s' % self.mqueue_type.regex
else:
raise AppArmorBug('Empty type in mqueue rule')
if self.all_labels:
label = ''
elif self.label:
label = ' label=%s' % quote_if_needed(self.label.regex)
else:
raise AppArmorBug('Empty label in mqueue rule')
if self.all_mqueue_names:
mqueue_name = ''
elif self.mqueue_name:
mqueue_name = ' %s' % self.mqueue_name.regex
else:
raise AppArmorBug('Empty mqueue_name in mqueue rule')
return('%s%smqueue%s%s%s%s,%s' % (space, self.modifiers_str(), access, mqueue_type, label, mqueue_name, self.comment))
def _is_covered_localvars(self, other_rule):
'''check if other_rule is covered by this rule object'''
if not self._is_covered_list(self.access, self.all_access, other_rule.access, other_rule.all_access, 'access'):
return False
if not self._is_covered_aare(self.mqueue_type, self.all_mqueue_types, other_rule.mqueue_type, other_rule.all_mqueue_types, 'mqueue_type'):
return False
if not self._is_covered_aare(self.label, self.all_labels, other_rule.label, other_rule.all_labels, 'label'):
return False
if not self._is_covered_aare(self.mqueue_name, self.all_mqueue_names, other_rule.mqueue_name, other_rule.all_mqueue_names, 'mqueue_name'):
return False
# still here? -> then it is covered
return True
def _is_equal_localvars(self, rule_obj, strict):
'''compare if rule-specific variables are equal'''
if (self.access != rule_obj.access or
self.all_access != rule_obj.all_access):
return False
if not self._is_equal_aare(self.mqueue_type, self.all_mqueue_types, rule_obj.mqueue_type, rule_obj.all_mqueue_types, 'mqueue_type'):
return False
if not self._is_equal_aare(self.label, self.all_labels, rule_obj.label, rule_obj.all_labels, 'label'):
return False
if not self._is_equal_aare(self.mqueue_name, self.all_mqueue_names, rule_obj.mqueue_name, rule_obj.all_mqueue_names, 'mqueue_name'):
return False
return True
def _logprof_header_localvars(self):
access = logprof_value_or_all(self.access, self.all_access)
mqueue_type = logprof_value_or_all(self.mqueue_type, self.all_mqueue_types)
label = logprof_value_or_all(self.label, self.all_labels)
mqueue_name = logprof_value_or_all(self.mqueue_name, self.all_mqueue_names)
return (
_('Access mode'), access,
_('Type'), mqueue_type,
_('Label'), label,
_('Message queue name'), mqueue_name
)
class MessageQueueRuleset(BaseRuleset):
'''Class to handle and store a collection of mqueue rules'''
def get_glob(self, path_or_rule):
'''Return the next possible glob. For mqueue rules, that means removing access, label or mqueue_name'''
# XXX only remove one part, not all
return 'mqueue,'

258
utils/test/test-mqueue.py Normal file
View File

@ -0,0 +1,258 @@
#!/usr/bin/python3
# ----------------------------------------------------------------------
# Copyright (C) 2022 Canonical, Ltd.
#
# 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 as 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.
#
# ----------------------------------------------------------------------
import unittest
from collections import namedtuple
from common_test import AATest, setup_all_loops
from apparmor.rule.mqueue import MessageQueueRule, MessageQueueRuleset
from apparmor.common import AppArmorException, AppArmorBug
from apparmor.translations import init_translation
_ = init_translation()
class MessageQueueTestParse(AATest):
tests = (
# access type label mqueue_name audit deny allow comment
('mqueue,' , MessageQueueRule(MessageQueueRule.ALL, MessageQueueRule.ALL, MessageQueueRule.ALL, MessageQueueRule.ALL, False, False, False, '')),
('mqueue create,' , MessageQueueRule(('create'), MessageQueueRule.ALL, MessageQueueRule.ALL, MessageQueueRule.ALL, False, False, False, '')),
('mqueue (create,open,delete),' , MessageQueueRule(('create', 'open', 'delete'), MessageQueueRule.ALL, MessageQueueRule.ALL, MessageQueueRule.ALL, False, False, False, '')),
('mqueue (getattr,setattr),' , MessageQueueRule(('getattr', 'setattr'), MessageQueueRule.ALL, MessageQueueRule.ALL, MessageQueueRule.ALL, False, False, False, '')),
('mqueue (write,read),' , MessageQueueRule(('write', 'read'), MessageQueueRule.ALL, MessageQueueRule.ALL, MessageQueueRule.ALL, False, False, False, '')),
('mqueue (open,delete),' , MessageQueueRule(('open', 'delete'), MessageQueueRule.ALL, MessageQueueRule.ALL, MessageQueueRule.ALL, False, False, False, '')),
('mqueue write label=foo,' , MessageQueueRule(('write'), MessageQueueRule.ALL, 'foo', MessageQueueRule.ALL, False, False, False, '')),
('mqueue read label=foo /queue,' , MessageQueueRule(('read'), MessageQueueRule.ALL, 'foo', '/queue', False, False, False, '')),
('audit mqueue read label=foo /queue,' , MessageQueueRule(('read'), MessageQueueRule.ALL, 'foo', '/queue', True, False, False, '')),
('deny mqueue rw label=foo /queue,' , MessageQueueRule(('rw'), MessageQueueRule.ALL, 'foo', '/queue', False, True, False, '')),
('audit allow mqueue r label=foo /queue,', MessageQueueRule(('r'), MessageQueueRule.ALL, 'foo', '/queue', True, False, True, '')),
('mqueue w label=foo 1234, # cmt' , MessageQueueRule(('w'), MessageQueueRule.ALL, 'foo', '1234', False, False, False, ' # cmt')),
('mqueue wr 1234,' , MessageQueueRule(('wr'), MessageQueueRule.ALL, MessageQueueRule.ALL, '1234', False, False, False, '')),
('mqueue 1234,' , MessageQueueRule(MessageQueueRule.ALL, MessageQueueRule.ALL, MessageQueueRule.ALL, '1234', False, False, False, '')),
('mqueue type=sysv,' , MessageQueueRule(MessageQueueRule.ALL, 'sysv', MessageQueueRule.ALL, MessageQueueRule.ALL, False, False, False, '')),
('mqueue type=posix,' , MessageQueueRule(MessageQueueRule.ALL, 'posix', MessageQueueRule.ALL, MessageQueueRule.ALL, False, False, False, '')),
('mqueue type=sysv 1234,' , MessageQueueRule(MessageQueueRule.ALL, 'sysv', MessageQueueRule.ALL, '1234', False, False, False, '')),
('mqueue type=posix /queue,' , MessageQueueRule(MessageQueueRule.ALL, 'posix', MessageQueueRule.ALL, '/queue', False, False, False, '')),
('mqueue open type=sysv label=foo 1234,' , MessageQueueRule(('open'), 'sysv', 'foo', '1234', False, False, False, '')),
)
def _run_test(self, rawrule, expected):
self.assertTrue(MessageQueueRule.match(rawrule))
obj = MessageQueueRule.create_instance(rawrule)
expected.raw_rule = rawrule.strip()
self.assertTrue(obj.is_equal(expected, True))
class MessageQueueTestParseInvalid(AATest):
tests = (
('mqueue label=,' , AppArmorException),
('mqueue invalidaccess /queuename,', AppArmorException),
('mqueue invalidqueuename,' , AppArmorException),
('mqueue invalidqueuename1234,' , AppArmorException),
('mqueue foo label foo bar,' , AppArmorException),
('mqueue type=,' , AppArmorException),
('mqueue type=sysv /foo,' , AppArmorException),
('mqueue type=posix 1234,' , AppArmorException),
)
def _run_test(self, rawrule, expected):
self.assertTrue(MessageQueueRule.match(rawrule)) # the above invalid rules still match the main regex!
with self.assertRaises(expected):
MessageQueueRule.create_instance(rawrule)
def test_parse_fail(self):
with self.assertRaises(AppArmorException):
MessageQueueRule.create_instance('foo,')
def test_diff_non_mqueuerule(self):
exp = namedtuple('exp', ('audit', 'deny'))
obj = MessageQueueRule(('open'), 'posix', 'bar', '/foo')
with self.assertRaises(AppArmorBug):
obj.is_equal(exp(False, False), False)
def test_diff_access(self):
obj1 = MessageQueueRule(('open'), 'posix', 'bar', '/foo')
obj2 = MessageQueueRule(('create'), 'posix', 'bar', '/foo')
self.assertFalse(obj1.is_equal(obj2, False))
def test_diff_type(self):
obj1 = MessageQueueRule(('open'), 'sysv', 'bar', MessageQueueRule.ALL)
obj2 = MessageQueueRule(('open'), 'posix', 'inv', MessageQueueRule.ALL)
self.assertFalse(obj1.is_equal(obj2, False))
def test_diff_label(self):
obj1 = MessageQueueRule(('open'), 'posix', 'bar', '/foo')
obj2 = MessageQueueRule(('open'), 'posix', 'inv', '/foo')
self.assertFalse(obj1.is_equal(obj2, False))
def test_diff_mqueue_name(self):
obj1 = MessageQueueRule(('open'), MessageQueueRule.ALL, 'bar', '/foo')
obj2 = MessageQueueRule(('open'), MessageQueueRule.ALL, 'bar', '123')
self.assertFalse(obj1.is_equal(obj2, False))
class InvalidMessageQueueInit(AATest):
tests = (
# init params expected exception
(('write', 'sysv', '', '/foo'), AppArmorBug), # empty label
(('write', '', 'bar', '/foo'), AppArmorBug), # empty type
(('', 'sysv', 'bar', '/foo'), AppArmorBug), # empty access
(('write', 'sysv', 'bar', ''), AppArmorBug), # empty mqueue_name
((' ', 'sysv', 'bar', '/foo'), AppArmorBug), # whitespace access
(('write', ' ', 'bar', '/foo'), AppArmorBug), # whitespace type
(('write', 'sysv', ' ', '/foo'), AppArmorBug), # whitespace label
(('write', 'sysv', 'bar', ' '), AppArmorBug), # whitespace mqueue_name
(('xyxy', 'sysv', 'bar', '/foo'), AppArmorException), # invalid access
((dict(), '', 'bar', '/foo'), AppArmorBug), # wrong type for access
((None, '', 'bar', '/foo'), AppArmorBug), # wrong type for access
(('write', dict(), 'bar', '/foo'), AppArmorBug), # wrong type for type
(('write', None, 'bar', '/foo'), AppArmorBug), # wrong type for type
(('write', '', dict(), '/foo'), AppArmorBug), # wrong type for label
(('write', '', None, '/foo'), AppArmorBug), # wrong type for label
(('write', '', 'bar', dict()), AppArmorBug), # wrong type for mqueue_name
(('write', '', 'bar', None), AppArmorBug), # wrong type for mqueue_name
)
def _run_test(self, params, expected):
with self.assertRaises(expected):
MessageQueueRule(*params)
def test_missing_params_1(self):
with self.assertRaises(TypeError):
MessageQueueRule()
def test_missing_params_2(self):
with self.assertRaises(TypeError):
MessageQueueRule('r')
def test_missing_params_3(self):
with self.assertRaises(TypeError):
MessageQueueRule('r', 'sysv')
def test_missing_params_4(self):
with self.assertRaises(TypeError):
MessageQueueRule('r', 'sysv', 'foo')
class WriteMessageQueueTestAATest(AATest):
tests = (
# raw rule clean rule
(' mqueue , # foo ' , 'mqueue, # foo'),
(' audit mqueue create,' , 'audit mqueue create,'),
(' audit mqueue (open ),' , 'audit mqueue open,'),
(' audit mqueue (delete , read ),' , 'audit mqueue (delete read),'),
(' deny mqueue write label=bar,# foo bar', 'deny mqueue write label=bar, # foo bar'),
(' deny mqueue open ,# foo bar' , 'deny mqueue open, # foo bar'),
(' allow mqueue label=tst ,# foo bar' , 'allow mqueue label=tst, # foo bar'),
('mqueue,' , 'mqueue,'),
('mqueue (read),' , 'mqueue read,'),
('mqueue (create),' , 'mqueue create,'),
('mqueue (write read),' , 'mqueue (read write),'),
('mqueue (open,create,open,delete,write,read),' , 'mqueue (create delete open read write),'),
('mqueue r,' , 'mqueue r,'),
('mqueue w,' , 'mqueue w,'),
('mqueue rw,' , 'mqueue rw,'),
('mqueue delete label="tst",' , 'mqueue delete label="tst",'),
('mqueue (getattr) label=bar,' , 'mqueue getattr label=bar,'),
('mqueue getattr /foo,' , 'mqueue getattr /foo,'),
('mqueue (setattr getattr) 1234,' , 'mqueue (getattr setattr) 1234,'),
('mqueue wr label=tst 1234,' , 'mqueue wr label=tst 1234,'),
('mqueue wr type=sysv label=tst 1234,' , 'mqueue wr type=sysv label=tst 1234,'),
('mqueue wr type=posix label=tst /foo,' , 'mqueue wr type=posix label=tst /foo,'),
)
def _run_test(self, rawrule, expected):
self.assertTrue(MessageQueueRule.match(rawrule))
obj = MessageQueueRule.create_instance(rawrule)
clean = obj.get_clean()
raw = obj.get_raw()
self.assertEqual(expected.strip(), clean, 'unexpected clean rule')
self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule')
def test_write_manually(self):
obj = MessageQueueRule('setattr', 'posix', 'bar', '/foo', allow_keyword=True)
expected = ' allow mqueue setattr type=posix label=bar /foo,'
self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule')
self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule')
def test_write_invalid_access(self):
obj = MessageQueueRule('setattr', 'posix', 'bar', '/foo')
obj.access = ''
with self.assertRaises(AppArmorBug):
obj.get_clean()
def test_write_invalid_type(self):
obj = MessageQueueRule('setattr', 'posix', 'bar', '/foo')
obj.mqueue_type = ''
with self.assertRaises(AppArmorBug):
obj.get_clean()
def test_write_invalid_label(self):
obj = MessageQueueRule('setattr', 'posix', 'bar', '/foo')
obj.label = ''
with self.assertRaises(AppArmorBug):
obj.get_clean()
def test_write_invalid_mqueue_name(self):
obj = MessageQueueRule('setattr', 'posix', 'bar', '/foo')
obj.mqueue_name = ''
with self.assertRaises(AppArmorBug):
obj.get_clean()
class MessageQueueIsCoveredTest(AATest):
def test_is_covered(self):
obj = MessageQueueRule(('create'), MessageQueueRule.ALL, 'f*', MessageQueueRule.ALL)
self.assertTrue(obj.is_covered(MessageQueueRule(('create'), 'sysv', 'f*', '1234')))
self.assertTrue(obj.is_covered(MessageQueueRule(('create'), 'posix', 'f*', MessageQueueRule.ALL)))
self.assertTrue(obj.is_covered(MessageQueueRule(('create'), 'sysv', 'foo', MessageQueueRule.ALL)))
self.assertTrue(obj.is_covered(MessageQueueRule(('create'), 'sysv', 'foo', '1234')))
def test_is_not_covered(self):
obj = MessageQueueRule(('getattr'), 'sysv', 'f*', '1234')
self.assertFalse(obj.is_covered(MessageQueueRule(('create'), 'sysv', 'foo', MessageQueueRule.ALL)))
self.assertFalse(obj.is_covered(MessageQueueRule(('getattr'), 'posix', 'foo', MessageQueueRule.ALL)))
self.assertFalse(obj.is_covered(MessageQueueRule(('getattr'), 'sysv', 'bar', MessageQueueRule.ALL)))
self.assertFalse(obj.is_covered(MessageQueueRule(('getattr'), 'sysv', 'foo', '123')))
class MessageQueueLogprofHeaderTest(AATest):
tests = (
('mqueue,', [ _('Access mode'), _('ALL'), _('Type'), _('ALL'), _('Label'), _('ALL'), _('Message queue name'), _('ALL'), ]),
('mqueue (create,getattr) 12,', [ _('Access mode'), 'create getattr', _('Type'), _('ALL'), _('Label'), _('ALL'), _('Message queue name'), '12', ]),
('mqueue write label=bar,', [ _('Access mode'), 'write', _('Type'), _('ALL'), _('Label'), 'bar', _('Message queue name'), _('ALL'), ]),
('mqueue write type=sysv,', [ _('Access mode'), 'write', _('Type'), 'sysv', _('Label'), _('ALL'), _('Message queue name'), _('ALL'), ]),
('mqueue read type=posix,', [ _('Access mode'), 'read', _('Type'), 'posix', _('Label'), _('ALL'), _('Message queue name'), _('ALL'), ]),
('deny mqueue read /foo,', [_('Qualifier'), 'deny', _('Access mode'), 'read', _('Type'), _('ALL'), _('Label'), _('ALL'), _('Message queue name'), '/foo', ]),
('allow mqueue setattr,', [_('Qualifier'), 'allow', _('Access mode'), 'setattr', _('Type'), _('ALL'), _('Label'), _('ALL'), _('Message queue name'), _('ALL'), ]),
('audit mqueue r label=ba 12,', [_('Qualifier'), 'audit', _('Access mode'), 'r', _('Type'), _('ALL'), _('Label'), 'ba', _('Message queue name'), '12', ]),
('audit deny mqueue rw,', [_('Qualifier'), 'audit deny', _('Access mode'), 'rw', _('Type'), _('ALL'), _('Label'), _('ALL'), _('Message queue name'), _('ALL'), ]),
)
def _run_test(self, params, expected):
obj = MessageQueueRule.create_instance(params)
self.assertEqual(obj.logprof_header(), expected)
class MessageQueueGlobTestAATest(AATest):
def test_glob(self):
self.assertEqual(MessageQueueRuleset().get_glob('mqueue create,'), 'mqueue,')
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=1)