diff --git a/.gitignore b/.gitignore index 3ac06fb18..bac42bf27 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.err b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.in b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.in new file mode 100644 index 000000000..4bc393d58 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.in @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.out b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.out new file mode 100644 index 000000000..4c1d8be16 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.out @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.profile b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.profile new file mode 100644 index 000000000..f9a36a126 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_01.profile @@ -0,0 +1,4 @@ +/root/apparmor/tests/regression/apparmor/posix_mq_rcv { + mqueue create type=posix /queuename, + +} diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.err b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.in b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.in new file mode 100644 index 000000000..abd39024d --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.in @@ -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 + diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.out b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.out new file mode 100644 index 000000000..b38b68a03 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.out @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.profile b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.profile new file mode 100644 index 000000000..1aaeff6f2 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_02.profile @@ -0,0 +1,4 @@ +/root/apparmor/tests/regression/apparmor/posix_mq_rcv { + mqueue read type=posix /queuename, + +} diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.err b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.in b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.in new file mode 100644 index 000000000..538a5429f --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.in @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.out b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.out new file mode 100644 index 000000000..b21d11855 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.out @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.profile b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.profile new file mode 100644 index 000000000..af443f5d0 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_03.profile @@ -0,0 +1,4 @@ +/root/apparmor/tests/regression/apparmor/posix_mq_rcv { + mqueue delete type=posix /queuename, + +} diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.err b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.in b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.in new file mode 100644 index 000000000..ce2aa35aa --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.in @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.out b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.out new file mode 100644 index 000000000..3ea1ed8de --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.out @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.profile b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.profile new file mode 100644 index 000000000..44f5f21fe --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_04.profile @@ -0,0 +1,4 @@ +/root/apparmor/tests/regression/apparmor/sysv_mq_rcv { + mqueue create type=sysv 123, + +} diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.err b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.in b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.in new file mode 100644 index 000000000..8b36aa91b --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.in @@ -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" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.out b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.out new file mode 100644 index 000000000..fc7c58e8d --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.out @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.profile b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.profile new file mode 100644 index 000000000..f04e07e46 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_05.profile @@ -0,0 +1,4 @@ +/root/apparmor/tests/regression/apparmor/sysv_mq_snd { + mqueue open type=sysv 123, + +} diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.err b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.in b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.in new file mode 100644 index 000000000..b8f741333 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.in @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.out b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.out new file mode 100644 index 000000000..cb348a874 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.out @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.profile b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.profile new file mode 100644 index 000000000..3ab389d66 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_06.profile @@ -0,0 +1,4 @@ +/root/apparmor/tests/regression/apparmor/sysv_mq_rcv { + mqueue read type=sysv 123, + +} diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.err b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.in b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.in new file mode 100644 index 000000000..15e22e7ea --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.in @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.out b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.out new file mode 100644 index 000000000..739ae8eca --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.out @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.profile b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.profile new file mode 100644 index 000000000..bb4965179 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_07.profile @@ -0,0 +1,4 @@ +/root/apparmor/tests/regression/apparmor/sysv_mq_rcv { + mqueue delete type=sysv 123, + +} diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.err b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.in b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.in new file mode 100644 index 000000000..dd46a703c --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.in @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.out b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.out new file mode 100644 index 000000000..b6a1fa40a --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.out @@ -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 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.profile b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.profile new file mode 100644 index 000000000..9b220cff3 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_mqueue_08.profile @@ -0,0 +1,4 @@ +/root/apparmor/tests/regression/apparmor/sysv_mq_snd { + mqueue write type=sysv 123, + +} diff --git a/parser/Makefile b/parser/Makefile index 39bf38d3d..f95fd4af2 100644 --- a/parser/Makefile +++ b/parser/Makefile @@ -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 $@ diff --git a/parser/apparmor.d.pod b/parser/apparmor.d.pod index 0b6637674..fbd324bdc 100644 --- a/parser/apparmor.d.pod +++ b/parser/apparmor.d.pod @@ -123,7 +123,7 @@ B = [ ( I | I ',' | I ) B = ( I | I ) [ '\r' ] '\n' -B = ( I | I | I | I | I | I | I | I | I | I ) +B = ( I | I | I | I | I | I | I | I | I | I | I ) B = ( I | I | I ) @@ -176,6 +176,20 @@ B = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec' B = ( I | I ) ... +B = [ I ] 'mqueue' [ I ] [ I ] [ I ] [ I ] + +B = I | I + +B = '(' Comma or space separated list of I ')' + +B = ( 'r' | 'w' | 'rw' | 'read' | 'write' | 'create' | 'open' | 'delete' | 'getattr' | 'setattr' ) + +B = 'type' '=' ( 'posix' | 'sysv' ) + +B = 'label' '=' '(' '"' I '"' | I ')' + +B = I + B = [ I ] pivot_root [ oldroot=I ] [ I ] [ '-E' I ] B = I @@ -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) diff --git a/parser/mqueue.cc b/parser/mqueue.cc new file mode 100644 index 000000000..9fe122b57 --- /dev/null +++ b/parser/mqueue.cc @@ -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 +#include +#include +#include + +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; +} diff --git a/parser/mqueue.h b/parser/mqueue.h new file mode 100644 index 000000000..fdbc0d38d --- /dev/null +++ b/parser/mqueue.h @@ -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 */ diff --git a/parser/parser.h b/parser/parser.h index 992e3d833..a2e1a30be 100644 --- a/parser/parser.h +++ b/parser/parser.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; diff --git a/parser/parser_common.c b/parser/parser_common.c index db3d7b357..3f798ecfc 100644 --- a/parser/parser_common.c +++ b/parser/parser_common.c @@ -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; diff --git a/parser/parser_interface.c b/parser/parser_interface.c index 8ad3e840e..c98a1fa29 100644 --- a/parser/parser_interface.c +++ b/parser/parser_interface.c @@ -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); diff --git a/parser/parser_lex.l b/parser/parser_lex.l index 7798d8833..1a07ca91c 100644 --- a/parser/parser_lex.l +++ b/parser/parser_lex.l @@ -328,6 +328,7 @@ GT > %x INCLUDE_EXISTS %x ABI_MODE %x USERNS_MODE +%x MQUEUE_MODE %% @@ -340,7 +341,7 @@ GT > } %} -{ +{ {WS}+ { DUMP_PREPROCESS; /* Ignoring whitespace */ } } @@ -376,7 +377,7 @@ GT > yyterminate(); } -{ +{ (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); } } -{ +{ create { RETURN_TOKEN(TOK_CREATE); } } +{ + open { RETURN_TOKEN(TOK_OPEN); } + delete { RETURN_TOKEN(TOK_DELETE); } +} + +{ + getattr { RETURN_TOKEN(TOK_GETATTR); } + setattr { RETURN_TOKEN(TOK_SETATTR); } +} + { bind { RETURN_TOKEN(TOK_BIND); } } @@ -590,7 +599,7 @@ GT > tracedby { RETURN_TOKEN(TOK_TRACEDBY); } } -{ +{ read { RETURN_TOKEN(TOK_READ); } write { RETURN_TOKEN(TOK_WRITE); } {OPEN_PAREN} { @@ -606,7 +615,7 @@ GT > {ARROW} { RETURN_TOKEN(TOK_ARROW); } } -{ +{ ({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); } -{ +{ {END_OF_RULE} { if (YY_START != INITIAL) POP_NODUMP(); @@ -745,14 +757,14 @@ include/{WS} { } } -{ +{ \r?\n { DUMP_PREPROCESS; current_lineno++; } } -{ +{ (.|\n) { DUMP_PREPROCESS; /* Something we didn't expect */ @@ -788,4 +800,5 @@ unordered_map state_names = { STATE_TABLE_ENT(INCLUDE_EXISTS), STATE_TABLE_ENT(ABI_MODE), STATE_TABLE_ENT(USERNS_MODE), + STATE_TABLE_ENT(MQUEUE_MODE), }; diff --git a/parser/parser_main.c b/parser/parser_main.c index 9d9d70e37..83597ebdd 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -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) diff --git a/parser/parser_misc.c b/parser/parser_misc.c index 3b7aee75e..36d847ca2 100644 --- a/parser/parser_misc.c +++ b/parser/parser_misc.c @@ -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} diff --git a/parser/parser_policy.c b/parser/parser_policy.c index f18d0a13c..2f66ab26f 100644 --- a/parser/parser_policy.c +++ b/parser/parser_policy.c @@ -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)) diff --git a/parser/parser_regex.c b/parser/parser_regex.c index 9c614157a..8cd8f15d3 100644 --- a/parser/parser_regex.c +++ b/parser/parser_regex.c @@ -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; diff --git a/parser/parser_yacc.y b/parser/parser_yacc.y index 1ea38363b..38913bde5 100644 --- a/parser/parser_yacc.y +++ b/parser/parser_yacc.y @@ -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 userns_perms %type opt_userns_perm %type userns_rule +%type mqueue_perm +%type mqueue_perms +%type opt_mqueue_perm +%type 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 {} diff --git a/parser/policydb.h b/parser/policydb.h index 4d7420d51..860b4278d 100644 --- a/parser/policydb.h +++ b/parser/policydb.h @@ -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 */ diff --git a/parser/profile.h b/parser/profile.h index f54467c07..5606baa46 100644 --- a/parser/profile.h +++ b/parser/profile.h @@ -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"; diff --git a/parser/tst/simple_tests/mqueue/bad_01.sd b/parser/tst/simple_tests/mqueue/bad_01.sd new file mode 100644 index 000000000..6bad91687 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_01.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid label +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue label=, +} diff --git a/parser/tst/simple_tests/mqueue/bad_02.sd b/parser/tst/simple_tests/mqueue/bad_02.sd new file mode 100644 index 000000000..9645b63e9 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_02.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid type +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue type=, +} diff --git a/parser/tst/simple_tests/mqueue/bad_03.sd b/parser/tst/simple_tests/mqueue/bad_03.sd new file mode 100644 index 000000000..1fa98a1c4 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_03.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid queuename for type sysv +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue type=sysv /queuename, +} diff --git a/parser/tst/simple_tests/mqueue/bad_04.sd b/parser/tst/simple_tests/mqueue/bad_04.sd new file mode 100644 index 000000000..7e4b24d7a --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_04.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid queuename for type posix +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue type=posix 1234, +} diff --git a/parser/tst/simple_tests/mqueue/bad_05.sd b/parser/tst/simple_tests/mqueue/bad_05.sd new file mode 100644 index 000000000..c9156dc91 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_05.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid access name +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue invalidaccess /queuename, +} diff --git a/parser/tst/simple_tests/mqueue/bad_06.sd b/parser/tst/simple_tests/mqueue/bad_06.sd new file mode 100644 index 000000000..b586ece1f --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_06.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid type option - posix +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue type=posixfoo, +} diff --git a/parser/tst/simple_tests/mqueue/bad_07.sd b/parser/tst/simple_tests/mqueue/bad_07.sd new file mode 100644 index 000000000..60a73694a --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_07.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid type option - sysv +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue type=sysvfoo, +} diff --git a/parser/tst/simple_tests/mqueue/bad_08.sd b/parser/tst/simple_tests/mqueue/bad_08.sd new file mode 100644 index 000000000..44d6dbc62 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_08.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid type option +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue type=foo, +} diff --git a/parser/tst/simple_tests/mqueue/bad_09.sd b/parser/tst/simple_tests/mqueue/bad_09.sd new file mode 100644 index 000000000..eb3b6adfe --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_09.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid queuename - does not start with / +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue foo, +} diff --git a/parser/tst/simple_tests/mqueue/bad_10.sd b/parser/tst/simple_tests/mqueue/bad_10.sd new file mode 100644 index 000000000..eb7efe473 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_10.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue invalid queuename - not only numbers +#=EXRESULT FAIL +# + +/usr/bin/foo { + mqueue 1234foo, +} diff --git a/parser/tst/simple_tests/mqueue/bad_11.sd b/parser/tst/simple_tests/mqueue/bad_11.sd new file mode 100644 index 000000000..9205931c5 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/bad_11.sd @@ -0,0 +1,6 @@ +# +#=DESCRIPTION mqueue rule outside of a profile +#=EXRESULT FAIL +# + + mqueue, diff --git a/parser/tst/simple_tests/mqueue/ok_01.sd b/parser/tst/simple_tests/mqueue/ok_01.sd new file mode 100644 index 000000000..826fcfce8 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_01.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION mqueue generic rule +#=EXRESULT PASS +# +/usr/bin/foo { + mqueue, +} diff --git a/parser/tst/simple_tests/mqueue/ok_02.sd b/parser/tst/simple_tests/mqueue/ok_02.sd new file mode 100644 index 000000000..7ba3820f8 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_02.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION mqueue type option +#=EXRESULT PASS +# +/usr/bin/foo { + mqueue type=posix, + mqueue type=sysv, +} diff --git a/parser/tst/simple_tests/mqueue/ok_03.sd b/parser/tst/simple_tests/mqueue/ok_03.sd new file mode 100644 index 000000000..823f936ee --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_03.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION mqueue label option +#=EXRESULT PASS +# +/usr/bin/foo { + mqueue label=bar, +} diff --git a/parser/tst/simple_tests/mqueue/ok_04.sd b/parser/tst/simple_tests/mqueue/ok_04.sd new file mode 100644 index 000000000..02033d5db --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_04.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION mqueue valid sysv queue name +#=EXRESULT PASS +# +/usr/bin/foo { + mqueue 1234, +} diff --git a/parser/tst/simple_tests/mqueue/ok_05.sd b/parser/tst/simple_tests/mqueue/ok_05.sd new file mode 100644 index 000000000..6e9fd119f --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_05.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION mqueue valid posix queue name +#=EXRESULT PASS +# +/usr/bin/foo { + mqueue /bar, +} diff --git a/parser/tst/simple_tests/mqueue/ok_06.sd b/parser/tst/simple_tests/mqueue/ok_06.sd new file mode 100644 index 000000000..12e09d0c7 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_06.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION mqueue valid sysv queue name with type +#=EXRESULT PASS +# +/usr/bin/foo { + mqueue type=sysv 1234, +} diff --git a/parser/tst/simple_tests/mqueue/ok_07.sd b/parser/tst/simple_tests/mqueue/ok_07.sd new file mode 100644 index 000000000..85c158125 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_07.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION mqueue valid posix queue name with type +#=EXRESULT PASS +# +/usr/bin/foo { + mqueue type=posix /bar, +} diff --git a/parser/tst/simple_tests/mqueue/ok_08.sd b/parser/tst/simple_tests/mqueue/ok_08.sd new file mode 100644 index 000000000..adead59e8 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_08.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION mqueue type and label defined +#=EXRESULT PASS +# +/usr/bin/foo { + mqueue type=posix label=bar, +} diff --git a/parser/tst/simple_tests/mqueue/ok_09.sd b/parser/tst/simple_tests/mqueue/ok_09.sd new file mode 100644 index 000000000..a1c5e4c48 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_09.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION mqueue type, label and queue name defined +#=EXRESULT PASS +# +/usr/bin/foo { + mqueue type=posix label=bar /baz, +} diff --git a/parser/tst/simple_tests/mqueue/ok_10.sd b/parser/tst/simple_tests/mqueue/ok_10.sd new file mode 100644 index 000000000..6a2046489 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_10.sd @@ -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, +} diff --git a/parser/tst/simple_tests/mqueue/ok_11.sd b/parser/tst/simple_tests/mqueue/ok_11.sd new file mode 100644 index 000000000..373a7d0ba --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_11.sd @@ -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, +} diff --git a/parser/tst/simple_tests/mqueue/ok_12.sd b/parser/tst/simple_tests/mqueue/ok_12.sd new file mode 100644 index 000000000..e38963c84 --- /dev/null +++ b/parser/tst/simple_tests/mqueue/ok_12.sd @@ -0,0 +1,10 @@ +# +#=DESCRIPTION mqueue misc rules +#=EXRESULT PASS +# +/usr/bin/foo { + deny mqueue, + audit allow mqueue, + audit deny mqueue, + allow mqueue, +} diff --git a/tests/regression/apparmor/Makefile b/tests/regression/apparmor/Makefile index 8fa433c48..233a6eb9d 100644 --- a/tests/regression/apparmor/Makefile +++ b/tests/regression/apparmor/Makefile @@ -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} diff --git a/tests/regression/apparmor/mkprofile.pl b/tests/regression/apparmor/mkprofile.pl index 17313641a..d00b68369 100755 --- a/tests/regression/apparmor/mkprofile.pl +++ b/tests/regression/apparmor/mkprofile.pl @@ -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); } diff --git a/tests/regression/apparmor/posix_ipc.sh b/tests/regression/apparmor/posix_ipc.sh new file mode 100644 index 000000000..5fd10bc75 --- /dev/null +++ b/tests/regression/apparmor/posix_ipc.sh @@ -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 + + diff --git a/tests/regression/apparmor/posix_mq.h b/tests/regression/apparmor/posix_mq.h new file mode 100644 index 000000000..5b2376800 --- /dev/null +++ b/tests/regression/apparmor/posix_mq.h @@ -0,0 +1,31 @@ +#ifndef POSIX_MQ_H_ +#define POSIX_MQ_H_ + +#include +#include +#include +#include +#include +#include +#include + +#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_ */ diff --git a/tests/regression/apparmor/posix_mq.sh b/tests/regression/apparmor/posix_mq.sh new file mode 100755 index 000000000..5c827d468 --- /dev/null +++ b/tests/regression/apparmor/posix_mq.sh @@ -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 + diff --git a/tests/regression/apparmor/posix_mq_rcv.c b/tests/regression/apparmor/posix_mq_rcv.c new file mode 100644 index 000000000..f9ffc4d7e --- /dev/null +++ b/tests/regression/apparmor/posix_mq_rcv.c @@ -0,0 +1,306 @@ +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/tests/regression/apparmor/posix_mq_snd.c b/tests/regression/apparmor/posix_mq_snd.c new file mode 100644 index 000000000..e560b632f --- /dev/null +++ b/tests/regression/apparmor/posix_mq_snd.c @@ -0,0 +1,32 @@ +#include +#include + +#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; +} diff --git a/tests/regression/apparmor/sysv_ipc.sh b/tests/regression/apparmor/sysv_ipc.sh new file mode 100755 index 000000000..d68fcd594 --- /dev/null +++ b/tests/regression/apparmor/sysv_ipc.sh @@ -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 diff --git a/tests/regression/apparmor/sysv_mq.h b/tests/regression/apparmor/sysv_mq.h new file mode 100644 index 000000000..d716947c7 --- /dev/null +++ b/tests/regression/apparmor/sysv_mq.h @@ -0,0 +1,35 @@ +#ifndef SYSV_MQ_H_ +#define SYSV_MQ_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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_ */ diff --git a/tests/regression/apparmor/sysv_mq.sh b/tests/regression/apparmor/sysv_mq.sh new file mode 100755 index 000000000..a9a2739a3 --- /dev/null +++ b/tests/regression/apparmor/sysv_mq.sh @@ -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) diff --git a/tests/regression/apparmor/sysv_mq_rcv.c b/tests/regression/apparmor/sysv_mq_rcv.c new file mode 100644 index 000000000..903e58d62 --- /dev/null +++ b/tests/regression/apparmor/sysv_mq_rcv.c @@ -0,0 +1,187 @@ +#define _GNU_SOURCE +#include +#include +#include + +#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; +} diff --git a/tests/regression/apparmor/sysv_mq_snd.c b/tests/regression/apparmor/sysv_mq_snd.c new file mode 100644 index 000000000..35296072c --- /dev/null +++ b/tests/regression/apparmor/sysv_mq_snd.c @@ -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; +} diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index f9758b165..71d7ea795 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -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: diff --git a/utils/apparmor/logparser.py b/utils/apparmor/logparser.py index b7a706257..8ab276d93 100644 --- a/utils/apparmor/logparser.py +++ b/utils/apparmor/logparser.py @@ -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) diff --git a/utils/apparmor/profile_storage.py b/utils/apparmor/profile_storage.py index df39b2ac8..fee490c1b 100644 --- a/utils/apparmor/profile_storage.py +++ b/utils/apparmor/profile_storage.py @@ -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', diff --git a/utils/apparmor/regex.py b/utils/apparmor/regex.py index 5870dcfde..5dae78b27 100644 --- a/utils/apparmor/regex.py +++ b/utils/apparmor/regex.py @@ -52,6 +52,7 @@ RE_PROFILE_PTRACE = re.compile(RE_AUDIT_DENY + r'(ptrace\s*,|ptrace(?P
\ 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
\s+[^#]*)\s*,)' + RE_EOL) +RE_PROFILE_MQUEUE = re.compile(RE_AUDIT_DENY + r'(mqueue\s*,|mqueue(?P
\s+[^#]*)\s*,)' + RE_EOL) # match anything that's not " or #, or matching quotes with anything except quotes inside __re_no_or_quoted_hash = '([^#"]|"[^"]*")*' diff --git a/utils/apparmor/rule/mqueue.py b/utils/apparmor/rule/mqueue.py new file mode 100644 index 000000000..e444c53d3 --- /dev/null +++ b/utils/apparmor/rule/mqueue.py @@ -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' + 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,' diff --git a/utils/test/test-mqueue.py b/utils/test/test-mqueue.py new file mode 100644 index 000000000..6cd199a66 --- /dev/null +++ b/utils/test/test-mqueue.py @@ -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)