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:
commit
b5f558962f
5
.gitignore
vendored
5
.gitignore
vendored
@ -39,6 +39,7 @@ parser/libapparmor_re/hfa.o
|
||||
parser/libapparmor_re/libapparmor_re.a
|
||||
parser/libapparmor_re/parse.o
|
||||
parser/mount.o
|
||||
parser/mqueue.o
|
||||
parser/network.o
|
||||
parser/parser_alias.o
|
||||
parser/parser_common.o
|
||||
@ -265,6 +266,8 @@ tests/regression/apparmor/open
|
||||
tests/regression/apparmor/openat
|
||||
tests/regression/apparmor/pipe
|
||||
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_helper
|
||||
tests/regression/apparmor/pwrite
|
||||
@ -288,6 +291,8 @@ tests/regression/apparmor/syscall_setpriority
|
||||
tests/regression/apparmor/syscall_setscheduler
|
||||
tests/regression/apparmor/syscall_sysctl
|
||||
tests/regression/apparmor/sysctl_proc
|
||||
tests/regression/apparmor/sysv_mq_rcv
|
||||
tests/regression/apparmor/sysv_mq_snd
|
||||
tests/regression/apparmor/tcp
|
||||
tests/regression/apparmor/transition
|
||||
tests/regression/apparmor/unix_fd_client
|
||||
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
/root/apparmor/tests/regression/apparmor/posix_mq_rcv {
|
||||
mqueue create type=posix /queuename,
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
/root/apparmor/tests/regression/apparmor/posix_mq_rcv {
|
||||
mqueue read type=posix /queuename,
|
||||
|
||||
}
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
/root/apparmor/tests/regression/apparmor/posix_mq_rcv {
|
||||
mqueue delete type=posix /queuename,
|
||||
|
||||
}
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
/root/apparmor/tests/regression/apparmor/sysv_mq_rcv {
|
||||
mqueue create type=sysv 123,
|
||||
|
||||
}
|
@ -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"
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
/root/apparmor/tests/regression/apparmor/sysv_mq_snd {
|
||||
mqueue open type=sysv 123,
|
||||
|
||||
}
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
/root/apparmor/tests/regression/apparmor/sysv_mq_rcv {
|
||||
mqueue read type=sysv 123,
|
||||
|
||||
}
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
/root/apparmor/tests/regression/apparmor/sysv_mq_rcv {
|
||||
mqueue delete type=sysv 123,
|
||||
|
||||
}
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
/root/apparmor/tests/regression/apparmor/sysv_mq_snd {
|
||||
mqueue write type=sysv 123,
|
||||
|
||||
}
|
@ -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_alias.c common_optarg.c lib.c network.c \
|
||||
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 \
|
||||
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
|
||||
|
||||
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)
|
||||
$(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
|
||||
@echo \#define PARSER_VERSION \"$(VERSION)\" > .ver
|
||||
@mv -f .ver $@
|
||||
|
@ -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<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> )
|
||||
|
||||
@ -176,6 +176,20 @@ B<MOUNT FLAGS> = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec'
|
||||
|
||||
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<SOURCE FILEGLOB> = I<FILEGLOB>
|
||||
@ -1057,6 +1071,51 @@ Matches only:
|
||||
|
||||
=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
|
||||
|
||||
AppArmor mediates changing of the root filesystem through the pivot_root(2)
|
||||
|
278
parser/mqueue.cc
Normal file
278
parser/mqueue.cc
Normal 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
111
parser/mqueue.h
Normal 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 */
|
@ -345,6 +345,8 @@ extern int features_supports_unix;
|
||||
extern int features_supports_stacking;
|
||||
extern int features_supports_domain_xattr;
|
||||
extern int features_supports_userns;
|
||||
extern int features_supports_posix_mqueue;
|
||||
extern int features_supports_sysv_mqueue;
|
||||
extern int kernel_supports_oob;
|
||||
extern int conf_verbose;
|
||||
extern int conf_quiet;
|
||||
|
@ -79,6 +79,8 @@ int features_supports_ptrace = 0; /* kernel supports ptrace rules */
|
||||
int features_supports_stacking = 0; /* kernel supports stacking */
|
||||
int features_supports_domain_xattr = 0; /* x attachment cond */
|
||||
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 conf_verbose = 0;
|
||||
int conf_quiet = 0;
|
||||
|
@ -420,7 +420,7 @@ void sd_serialize_profile(std::ostringstream &buf, Profile *profile,
|
||||
|
||||
sd_write_struct(buf, "flags");
|
||||
/* 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->flags.audit);
|
||||
sd_write_structend(buf);
|
||||
|
@ -328,6 +328,7 @@ GT >
|
||||
%x INCLUDE_EXISTS
|
||||
%x ABI_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 */ }
|
||||
}
|
||||
|
||||
@ -376,7 +377,7 @@ GT >
|
||||
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}*\( {
|
||||
/* 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
|
||||
@ -560,17 +561,25 @@ GT >
|
||||
listen { RETURN_TOKEN(TOK_LISTEN); }
|
||||
accept { RETURN_TOKEN(TOK_ACCEPT); }
|
||||
connect { RETURN_TOKEN(TOK_CONNECT); }
|
||||
getattr { RETURN_TOKEN(TOK_GETATTR); }
|
||||
setattr { RETURN_TOKEN(TOK_SETATTR); }
|
||||
getopt { RETURN_TOKEN(TOK_GETOPT); }
|
||||
setopt { RETURN_TOKEN(TOK_SETOPT); }
|
||||
shutdown { RETURN_TOKEN(TOK_SHUTDOWN); }
|
||||
}
|
||||
|
||||
<UNIX_MODE,USERNS_MODE>{
|
||||
<UNIX_MODE,USERNS_MODE,MQUEUE_MODE>{
|
||||
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>{
|
||||
bind { RETURN_TOKEN(TOK_BIND); }
|
||||
}
|
||||
@ -590,7 +599,7 @@ GT >
|
||||
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); }
|
||||
write { RETURN_TOKEN(TOK_WRITE); }
|
||||
{OPEN_PAREN} {
|
||||
@ -606,7 +615,7 @@ GT >
|
||||
{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}) {
|
||||
yylval.id = processid(yytext, yyleng);
|
||||
RETURN_TOKEN(TOK_ID);
|
||||
@ -731,13 +740,16 @@ include/{WS} {
|
||||
case TOK_USERNS:
|
||||
state = USERNS_MODE;
|
||||
break;
|
||||
case TOK_MQUEUE:
|
||||
state = MQUEUE_MODE;
|
||||
break;
|
||||
default: /* nothing */
|
||||
break;
|
||||
}
|
||||
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} {
|
||||
if (YY_START != INITIAL)
|
||||
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 {
|
||||
DUMP_PREPROCESS;
|
||||
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) {
|
||||
DUMP_PREPROCESS;
|
||||
/* Something we didn't expect */
|
||||
@ -788,4 +800,5 @@ unordered_map<int, string> state_names = {
|
||||
STATE_TABLE_ENT(INCLUDE_EXISTS),
|
||||
STATE_TABLE_ENT(ABI_MODE),
|
||||
STATE_TABLE_ENT(USERNS_MODE),
|
||||
STATE_TABLE_ENT(MQUEUE_MODE),
|
||||
};
|
||||
|
@ -944,6 +944,12 @@ void set_supported_features()
|
||||
features_supports_userns = features_intersect(kernel_features,
|
||||
policy_features,
|
||||
"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)
|
||||
|
@ -121,6 +121,9 @@ static struct keyword_table keyword_table[] = {
|
||||
{"readby", TOK_READBY},
|
||||
{"abi", TOK_ABI},
|
||||
{"userns", TOK_USERNS},
|
||||
{"mqueue", TOK_MQUEUE},
|
||||
{"delete", TOK_DELETE},
|
||||
{"open", TOK_OPEN},
|
||||
|
||||
/* terminate */
|
||||
{NULL, 0}
|
||||
|
@ -243,7 +243,7 @@ void post_process_rule_entries(Profile *prof)
|
||||
static int profile_add_hat_rules(Profile *prof)
|
||||
{
|
||||
/* 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;
|
||||
|
||||
if (!add_proc_access(prof, CHANGEHAT_PATH))
|
||||
|
@ -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_net_unix = CLASS_SUB_STR(AA_CLASS_NET, AF_UNIX);
|
||||
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)
|
||||
{
|
||||
@ -981,6 +983,12 @@ int process_profile_policydb(Profile *prof)
|
||||
if (features_supports_userns &&
|
||||
!prof->policy.rules->add_rule(mediates_ns, 0, AA_MAY_READ, 0, dfaflags))
|
||||
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) {
|
||||
int xmatch_len = 0;
|
||||
|
@ -143,6 +143,8 @@ void add_local_entry(Profile *prof);
|
||||
%token TOK_READBY
|
||||
%token TOK_ABI
|
||||
%token TOK_USERNS
|
||||
%token TOK_MQUEUE
|
||||
%token TOK_DELETE
|
||||
|
||||
/* rlimits */
|
||||
%token TOK_RLIMIT
|
||||
@ -179,6 +181,7 @@ void add_local_entry(Profile *prof);
|
||||
#include "ptrace.h"
|
||||
#include "af_unix.h"
|
||||
#include "userns.h"
|
||||
#include "mqueue.h"
|
||||
}
|
||||
|
||||
%union {
|
||||
@ -196,6 +199,7 @@ void add_local_entry(Profile *prof);
|
||||
ptrace_rule *ptrace_entry;
|
||||
unix_rule *unix_entry;
|
||||
userns_rule *userns_entry;
|
||||
mqueue_rule *mqueue_entry;
|
||||
|
||||
flagvals flags;
|
||||
int fmode;
|
||||
@ -279,6 +283,10 @@ void add_local_entry(Profile *prof);
|
||||
%type <fmode> userns_perms
|
||||
%type <fmode> opt_userns_perm
|
||||
%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'."));
|
||||
|
||||
if ($1 == 2)
|
||||
prof->flags.hat = 1;
|
||||
prof->flags.flags |= FLAG_HAT;
|
||||
$$ = prof;
|
||||
};
|
||||
|
||||
@ -440,7 +448,7 @@ hat: hat_start profile_base
|
||||
if ($2->xattrs.list)
|
||||
yyerror("hat profiles can't use xattrs matches");
|
||||
|
||||
prof->flags.hat = 1;
|
||||
prof->flags.flags |= FLAG_HAT;
|
||||
$$ = prof;
|
||||
};
|
||||
|
||||
@ -920,6 +928,22 @@ rules: rules opt_prefix capability
|
||||
$$ = $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
|
||||
{
|
||||
PDEBUG("Matched: hat rule\n");
|
||||
@ -1591,6 +1615,62 @@ userns_rule: TOK_USERNS opt_userns_perm opt_conds TOK_END_OF_RULE
|
||||
$$ = 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 {}
|
||||
| TOK_HAT {}
|
||||
|
||||
|
@ -34,6 +34,8 @@
|
||||
#define AA_CLASS_SIGNAL 10
|
||||
#define AA_CLASS_NETV8 14
|
||||
#define AA_CLASS_LABEL 16
|
||||
#define AA_CLASS_POSIX_MQUEUE 17
|
||||
#define AA_CLASS_SYSV_MQUEUE 18
|
||||
#define AA_CLASS_NS 21
|
||||
|
||||
/* defined in libapparmor's apparmor.h #define AA_CLASS_DBUS 32 */
|
||||
|
@ -110,9 +110,13 @@ static inline enum profile_mode str_to_mode(const char *str)
|
||||
return MODE_UNSPECIFIED;
|
||||
};
|
||||
|
||||
#define FLAG_HAT 1
|
||||
#define FLAG_DEBUG1 2
|
||||
#define FLAG_DEBUG2 4
|
||||
|
||||
class flagvals {
|
||||
public:
|
||||
int hat;
|
||||
int flags;
|
||||
enum profile_mode mode;
|
||||
int audit;
|
||||
int path;
|
||||
@ -124,7 +128,7 @@ public:
|
||||
if (audit)
|
||||
os << ", Audit";
|
||||
|
||||
if (hat)
|
||||
if (flags & FLAG_HAT)
|
||||
os << ", Hat";
|
||||
|
||||
os << "\n";
|
||||
|
8
parser/tst/simple_tests/mqueue/bad_01.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_01.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid label
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue label=,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/bad_02.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_02.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid type
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue type=,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/bad_03.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_03.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid queuename for type sysv
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue type=sysv /queuename,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/bad_04.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_04.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid queuename for type posix
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue type=posix 1234,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/bad_05.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_05.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid access name
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue invalidaccess /queuename,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/bad_06.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_06.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid type option - posix
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue type=posixfoo,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/bad_07.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_07.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid type option - sysv
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue type=sysvfoo,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/bad_08.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_08.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid type option
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue type=foo,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/bad_09.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_09.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid queuename - does not start with /
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue foo,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/bad_10.sd
Normal file
8
parser/tst/simple_tests/mqueue/bad_10.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue invalid queuename - not only numbers
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
/usr/bin/foo {
|
||||
mqueue 1234foo,
|
||||
}
|
6
parser/tst/simple_tests/mqueue/bad_11.sd
Normal file
6
parser/tst/simple_tests/mqueue/bad_11.sd
Normal file
@ -0,0 +1,6 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue rule outside of a profile
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
|
||||
mqueue,
|
7
parser/tst/simple_tests/mqueue/ok_01.sd
Normal file
7
parser/tst/simple_tests/mqueue/ok_01.sd
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue generic rule
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
mqueue,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/ok_02.sd
Normal file
8
parser/tst/simple_tests/mqueue/ok_02.sd
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue type option
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
mqueue type=posix,
|
||||
mqueue type=sysv,
|
||||
}
|
7
parser/tst/simple_tests/mqueue/ok_03.sd
Normal file
7
parser/tst/simple_tests/mqueue/ok_03.sd
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue label option
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
mqueue label=bar,
|
||||
}
|
7
parser/tst/simple_tests/mqueue/ok_04.sd
Normal file
7
parser/tst/simple_tests/mqueue/ok_04.sd
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue valid sysv queue name
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
mqueue 1234,
|
||||
}
|
7
parser/tst/simple_tests/mqueue/ok_05.sd
Normal file
7
parser/tst/simple_tests/mqueue/ok_05.sd
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue valid posix queue name
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
mqueue /bar,
|
||||
}
|
7
parser/tst/simple_tests/mqueue/ok_06.sd
Normal file
7
parser/tst/simple_tests/mqueue/ok_06.sd
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue valid sysv queue name with type
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
mqueue type=sysv 1234,
|
||||
}
|
7
parser/tst/simple_tests/mqueue/ok_07.sd
Normal file
7
parser/tst/simple_tests/mqueue/ok_07.sd
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue valid posix queue name with type
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
mqueue type=posix /bar,
|
||||
}
|
7
parser/tst/simple_tests/mqueue/ok_08.sd
Normal file
7
parser/tst/simple_tests/mqueue/ok_08.sd
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue type and label defined
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
mqueue type=posix label=bar,
|
||||
}
|
7
parser/tst/simple_tests/mqueue/ok_09.sd
Normal file
7
parser/tst/simple_tests/mqueue/ok_09.sd
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue type, label and queue name defined
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
mqueue type=posix label=bar /baz,
|
||||
}
|
14
parser/tst/simple_tests/mqueue/ok_10.sd
Normal file
14
parser/tst/simple_tests/mqueue/ok_10.sd
Normal 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,
|
||||
}
|
8
parser/tst/simple_tests/mqueue/ok_11.sd
Normal file
8
parser/tst/simple_tests/mqueue/ok_11.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/mqueue/ok_12.sd
Normal file
10
parser/tst/simple_tests/mqueue/ok_12.sd
Normal file
@ -0,0 +1,10 @@
|
||||
#
|
||||
#=DESCRIPTION mqueue misc rules
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
deny mqueue,
|
||||
audit allow mqueue,
|
||||
audit deny mqueue,
|
||||
allow mqueue,
|
||||
}
|
@ -115,6 +115,8 @@ SRC=access.c \
|
||||
openat.c \
|
||||
pipe.c \
|
||||
pivot_root.c \
|
||||
posix_mq_rcv.c \
|
||||
posix_mq_snd.c \
|
||||
ptrace.c \
|
||||
ptrace_helper.c \
|
||||
pwrite.c \
|
||||
@ -135,6 +137,8 @@ SRC=access.c \
|
||||
syscall_setdomainname.c \
|
||||
syscall_setscheduler.c \
|
||||
sysctl_proc.c \
|
||||
sysv_mq_rcv.c \
|
||||
sysv_mq_snd.c \
|
||||
tcp.c \
|
||||
transition.c \
|
||||
unix_fd_client.c \
|
||||
@ -237,6 +241,7 @@ TESTS=aa_exec \
|
||||
openat \
|
||||
pipe \
|
||||
pivot_root \
|
||||
posix_ipc \
|
||||
ptrace \
|
||||
pwrite \
|
||||
query_label \
|
||||
@ -250,6 +255,7 @@ TESTS=aa_exec \
|
||||
setattr \
|
||||
symlink \
|
||||
syscall \
|
||||
sysv_ipc \
|
||||
tcp \
|
||||
unix_fd_server \
|
||||
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
|
||||
${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
|
||||
${CC} ${CFLAGS} ${TRANSITION_CFLAGS} ${LDFLAGS} $< -o $@ ${LDLIBS}
|
||||
|
||||
|
@ -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($) {
|
||||
my $hat = shift;
|
||||
|
||||
@ -492,6 +512,8 @@ sub gen_from_args() {
|
||||
gen_xattr($rule);
|
||||
} elsif ($rule =~ /^path:/) {
|
||||
gen_path($rule);
|
||||
} elsif ($rule =~ /^mqueue:/) {
|
||||
gen_mqueue($rule);
|
||||
} else {
|
||||
gen_file($rule, $qualifier);
|
||||
}
|
||||
|
12
tests/regression/apparmor/posix_ipc.sh
Normal file
12
tests/regression/apparmor/posix_ipc.sh
Normal 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
|
||||
|
||||
|
31
tests/regression/apparmor/posix_mq.h
Normal file
31
tests/regression/apparmor/posix_mq.h
Normal 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_ */
|
179
tests/regression/apparmor/posix_mq.sh
Executable file
179
tests/regression/apparmor/posix_mq.sh
Executable 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
|
||||
|
306
tests/regression/apparmor/posix_mq_rcv.c
Normal file
306
tests/regression/apparmor/posix_mq_rcv.c
Normal 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;
|
||||
}
|
32
tests/regression/apparmor/posix_mq_snd.c
Normal file
32
tests/regression/apparmor/posix_mq_snd.c
Normal 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;
|
||||
}
|
9
tests/regression/apparmor/sysv_ipc.sh
Executable file
9
tests/regression/apparmor/sysv_ipc.sh
Executable 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
|
35
tests/regression/apparmor/sysv_mq.h
Normal file
35
tests/regression/apparmor/sysv_mq.h
Normal 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_ */
|
171
tests/regression/apparmor/sysv_mq.sh
Executable file
171
tests/regression/apparmor/sysv_mq.sh
Executable 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)
|
187
tests/regression/apparmor/sysv_mq_rcv.c
Normal file
187
tests/regression/apparmor/sysv_mq_rcv.c
Normal 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;
|
||||
}
|
55
tests/regression/apparmor/sysv_mq_snd.c
Normal file
55
tests/regression/apparmor/sysv_mq_snd.c
Normal 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;
|
||||
}
|
@ -51,6 +51,7 @@ from apparmor.rule.network import NetworkRule
|
||||
from apparmor.rule.ptrace import PtraceRule
|
||||
from apparmor.rule.signal import SignalRule
|
||||
from apparmor.rule.userns import UserNamespaceRule
|
||||
from apparmor.rule.mqueue import MessageQueueRule
|
||||
from apparmor.translations import 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):
|
||||
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
|
||||
|
||||
|
||||
@ -2104,6 +2113,7 @@ def match_line_against_rule_classes(line, profile, file, lineno, in_preamble):
|
||||
'rlimit',
|
||||
'signal',
|
||||
'userns',
|
||||
'mqueue',
|
||||
):
|
||||
|
||||
if rule_name in ruletypes:
|
||||
|
@ -58,6 +58,7 @@ class ReadLog:
|
||||
'ptrace': hasher(),
|
||||
'signal': hasher(),
|
||||
'userns': hasher(),
|
||||
'mqueue': hasher(),
|
||||
}
|
||||
|
||||
def prefetch_next_log_entry(self):
|
||||
@ -188,7 +189,12 @@ class ReadLog:
|
||||
elif e['class'] and e['class'] == 'namespace':
|
||||
if e['denied_mask'].startswith('userns'):
|
||||
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':
|
||||
# Map c (create) and d (delete) to w (logging is more detailed than the profile language)
|
||||
|
@ -28,6 +28,7 @@ from apparmor.rule.ptrace import PtraceRule, PtraceRuleset
|
||||
from apparmor.rule.rlimit import RlimitRule, RlimitRuleset
|
||||
from apparmor.rule.signal import SignalRule, SignalRuleset
|
||||
from apparmor.rule.userns import UserNamespaceRule, UserNamespaceRuleset
|
||||
from apparmor.rule.mqueue import MessageQueueRule, MessageQueueRuleset
|
||||
from apparmor.translations import init_translation
|
||||
|
||||
_ = init_translation()
|
||||
@ -44,6 +45,7 @@ ruletypes = {
|
||||
'rlimit': {'rule': RlimitRule, 'ruleset': RlimitRuleset},
|
||||
'signal': {'rule': SignalRule, 'ruleset': SignalRuleset},
|
||||
'userns': {'rule': UserNamespaceRule, 'ruleset': UserNamespaceRuleset},
|
||||
'mqueue': {'rule': MessageQueueRule, 'ruleset': MessageQueueRuleset},
|
||||
}
|
||||
|
||||
|
||||
@ -188,6 +190,7 @@ class ProfileStorage:
|
||||
'network',
|
||||
'dbus',
|
||||
'mount',
|
||||
'mqueue',
|
||||
'signal',
|
||||
'ptrace',
|
||||
'pivot_root',
|
||||
|
@ -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_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_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
|
||||
__re_no_or_quoted_hash = '([^#"]|"[^"]*")*'
|
||||
|
230
utils/apparmor/rule/mqueue.py
Normal file
230
utils/apparmor/rule/mqueue.py
Normal 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
258
utils/test/test-mqueue.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user