A very peculiar thing happens with a certain program, let’s call it
irq_assigner
: When in operation, it performs some kind of action that
requires the DAC_OVERRIDE
capability:
avc: denied { dac_override } for capability=1 \
scontext=u:r:irq_assigner:s0 tcontext=u:r:irq_assigner:s0 tclass=capability
This is the program in question, /vendor/etc/init/irq_assigner.rc
:
service irq_assigner /vendor/bin/irq_assigner
socket irq_assigner seqpacket 660 root system
class core
user root
group root
disabled
on property:sys.boot_completed=1
enable irq_assigner
We checked all files it opens(which are few) and verified the UNIX
permissions(those in rwxrwxrwx
format) were all correct, i.e. root
had
sufficient permissions to access them.
Android’s SELinux stack has no option to print the file for which dac_override
was requested, so we need some trickery.
First, let’s try to find what even is causing the issue, the blunt way with a
WARN_ON
to dump a stack trace:
diff --git a/security/selinux/avc.c b/security/selinux/avc.c
index 84d9a2e2bbaf..c845db70ed73 100644
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c
@@ -734,6 +734,10 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a)
if (ad->selinux_audit_data->denied) {
audit_log_format(ab, " permissive=%u",
ad->selinux_audit_data->result ? 0 : 1);
+ if (ad->u.cap == 1) {
+ audit_log_format(ab, " cap=dac_override, dumping stack");
+ WARN_ON(ad->u.cap == 1);
+ }
}
}
Here we go then:
Call trace:
: Exception stack(0xffffffc04a68f6b0 to 0xffffffc04a68f7e0)
f6a0 : 0000000000000004 0000007fffffffff
: [<ffffff80083ab300>] avc_audit_post_callback+0x190/0x198
: [<ffffff80083cc314>] common_lsm_audit+0xa8/0x710
: [<ffffff80083ac008>] slow_avc_audit+0xa8/0xd4
: [<ffffff80083af5d0>] cred_has_capability+0x11c/0x140
: [<ffffff80083af640>] selinux_capable+0x4c/0x58
: [<ffffff80083a642c>] security_capable+0x64/0x94
: [<ffffff80080bdd38>] capable_wrt_inode_uidgid+0x40/0x98
: [<ffffff8008253020>] generic_permission.part.34+0xac/0xdc
: [<ffffff80082535a8>] __inode_permission2+0x70/0x104
: [<ffffff80082536b0>] inode_permission2+0x38/0x74
: [<ffffff80082567dc>] lookup_open+0x17c/0x588
: [<ffffff8008257018>] path_openat+0x430/0xaa4
: [<ffffff8008259744>] do_filp_open+0x70/0x104
: [<ffffff8008244648>] do_sys_open+0x154/0x268
: [<ffffff80082447e0>] SyS_openat+0x3c/0x48
: [<ffffff8008083e00>] el0_svc_naked+0x34/0x38
The access vector is a dir/file opened with wrong permissions, as expected.
After some probing, a good way to intercept the relavant call is to modify
generic_permission()
to print if a DAC_OVERRIDE
was denied:
diff --git a/fs/namei.c b/fs/namei.c
index d8f858484538..234d7cd1a572 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -39,6 +39,8 @@
#include <linux/init_task.h>
#include <asm/uaccess.h>
+#include <linux/printk.h>
+
#include "internal.h"
#include "mount.h"
@@ -341,8 +343,12 @@ int generic_permission(struct inode *inode, int mask)
if (S_ISDIR(inode->i_mode)) {
/* DACs are overridable for directories */
- if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
+ if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE)) {
return 0;
+ } else {
+ pr_err("dac_override(dir) denied for cap=%3o inode=%lu with uid=%u gid=%u",
+ mask&0777, inode->i_ino, inode->i_uid, inode->i_gid);
+ }
if (!(mask & MAY_WRITE))
if (capable_wrt_inode_uidgid(inode,
CAP_DAC_READ_SEARCH))
@@ -354,9 +360,21 @@ int generic_permission(struct inode *inode, int mask)
* Executable DACs are overridable when there is
* at least one exec bit set.
*/
- if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO))
- if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
+ /* IXUGO = (S_IXUSR|S_IXGRP|S_IXOTH) */
+ if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO)) {
+ if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE)) {
return 0;
+ } else {
+ /* struct inode { */
+ /* i_uid is of uid32_t = unsigned int */
+ /* i_gid same */
+ /* i_ino is of unsigned long */
+ /* } */
+ /* pr_err("dac_override denied for cap=%o inode=%lu with uid=%d gid=%d", */
+ pr_err("dac_override(file) denied for cap=%3o inode=%lu with uid=%u gid=%u",
+ mask&0777, inode->i_ino, inode->i_uid, inode->i_gid);
+ }
+ }
/*
* Searching includes executable on directories, else just read.
With the logging and WARN_ON
in place, time for a test run:
------------[ cut here ]------------
WARNING: CPU: 3 PID: 1681 at avc_audit_post_callback+0x190/0x198
Modules linked in:
CPU: 3 PID: 1681 Comm: irq_assigner Tainted: G W 4.9.160-ge2af85baed78-dirty #6
name: SoMC Kagura-ROW (DT)
PC is at avc_audit_post_callback+0x190/0x198
LR is at avc_audit_post_callback+0x184/0x198
[...]
---[ end trace 0db9c115d7e8753c ]---
: dac_override(dir) denied for cap= 3 inode=4026533027 with uid=0 gid=0
: dac_override(dir) denied for cap= 3 inode=4026531862 with uid=0 gid=0
: dac_override(dir) denied for cap= 3 inode=1146882 with uid=0 gid=0
irq_assigner: type=1400 audit(0.0:11): avc: denied { dac_override } for \
capability=1 scontext=u:r:irq_assigner:s0 tcontext=u:r:irq_assigner:s0 \
tclass=capability permissive=0 cap=dac_override, dumping stack
[...]
---[ end trace 0db9c115d7e87539 ]---
Call trace:
Exception stack(0xffffffc04a68f6b0 to 0xffffffc04a68f7e0)
[...]
dac_override(dir) denied for cap= 3 inode=4026532260 with uid=0 gid=0
------------[ cut here ]------------
WARNING: CPU: 2 PID: 1681 at avc_audit_post_callback+0x190/0x198
CPU: 2 PID: 1681 Comm: irq_assigner Tainted: G W 4.9.160-ge2af85baed78-dirty #6
PC is at avc_audit_post_callback+0x190/0x198
LR is at avc_audit_post_callback+0x184/0x198
[...]
Call trace:
[...]
dac_override(dir) denied for cap= 3 inode=4026532239 with uid=0 gid=0
The high inode
numbers in the 4.000.000.000
range correspond to directories in
/proc/irq
:
kagura:/ # find /proc -inum 4026533027 2&>/dev/null
/proc/irq/531
kagura:/ # find /proc -inum 4026532260 2&>/dev/null
/proc/irq/82
Permissions on /proc/irq
and its subdirs are dr-xr-xr-x
, as created by
proc_create_data() (parent dir with
S_IRUGO=(S_IRUSR|S_IRGRP|S_IROTH)).
Interesting, so we now the handling of dirs/files in /proc/irq/
is the issue.
Let’s strace
the relevant parts:
[...]
openat(AT_FDCWD, "/proc/irq/74/smp_affinity", O_RDONLY) = 8
fstat(8, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
read(8, "1\n", 1024) = 2
close(8) = 0
And later:
openat(AT_FDCWD, "/proc/irq/81/smp_affinity", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 8
fstat(8, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
write(8, "2", 1) = 1
close(8) = 0
Compare that to the behaviour of a different version that does not have the issue:
openat(AT_FDCWD, "/proc/irq/105/smp_affinity", O_RDWR) = 6
fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
write(6, "1", 1) = 1
close(6) = 0
Quite different…
The fix should be easy, change the fopen()
mode from
O_WRONLY|O_CREAT|O_TRUNC
to O_RDWR
(=0664
).