Skip to content

Commit 00555a4

Browse files
authored
Merge pull request #2 from Maxencejules/feat/daily-driver-audit-fixes
Daily-driver OS audit: close all 10 gap findings with live proofs
2 parents 94c7ae4 + c0584c4 commit 00555a4

52 files changed

Lines changed: 4846 additions & 944 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ $(X1_PROC_SOCK_ELF): $(OUT)/x1-proc-sock.o services/compat/linker.ld | $(OUT)
187187
$(LD) -nostdlib -static -T services/compat/linker.ld -o $@ $<
188188

189189
APP_BASE_SHELL_ELF = $(OUT)/app-base-shell.elf
190-
COREUTILS_ELFS = $(OUT)/app-echo.elf $(OUT)/app-cat.elf $(OUT)/app-ls.elf $(OUT)/app-ps.elf $(OUT)/app-wc.elf $(OUT)/app-nxprobe.elf $(OUT)/app-sigprobe.elf $(OUT)/app-fsperm.elf $(OUT)/app-asprobe.elf $(OUT)/app-forkprobe.elf $(OUT)/app-vmprobe.elf $(OUT)/app-timeprobe.elf $(OUT)/app-rngprobe.elf $(OUT)/app-sandboxprobe.elf $(OUT)/app-devprobe.elf $(OUT)/app-sysinfoprobe.elf $(OUT)/app-futexprobe.elf $(OUT)/app-lseekprobe.elf $(OUT)/app-sleepprobe.elf $(OUT)/app-gfxprobe.elf $(OUT)/app-waitprobe.elf $(OUT)/app-timerfdprobe.elf $(OUT)/app-dmesgprobe.elf $(OUT)/app-ptyprobe.elf $(OUT)/app-partprobe.elf $(OUT)/app-fatprobe.elf $(OUT)/app-page3probe.elf $(OUT)/app-beepprobe.elf $(OUT)/app-auditprobe.elf $(OUT)/app-fatlsprobe.elf $(OUT)/app-cryptprobe.elf $(OUT)/app-journalprobe.elf $(OUT)/app-userprobe.elf $(OUT)/app-compositorprobe.elf $(OUT)/app-dlprobe.elf $(OUT)/app-fatwrprobe.elf $(OUT)/app-fatbigprobe.elf $(OUT)/app-loginprobe.elf $(OUT)/app-cowfixprobe.elf $(OUT)/app-clonebrkprobe.elf $(OUT)/app-ondlprobe.elf $(OUT)/app-surfprobe.elf
190+
COREUTILS_ELFS = $(OUT)/app-echo.elf $(OUT)/app-cat.elf $(OUT)/app-ls.elf $(OUT)/app-ps.elf $(OUT)/app-wc.elf $(OUT)/app-nxprobe.elf $(OUT)/app-sigprobe.elf $(OUT)/app-fsperm.elf $(OUT)/app-asprobe.elf $(OUT)/app-forkprobe.elf $(OUT)/app-vmprobe.elf $(OUT)/app-timeprobe.elf $(OUT)/app-rngprobe.elf $(OUT)/app-sandboxprobe.elf $(OUT)/app-devprobe.elf $(OUT)/app-sysinfoprobe.elf $(OUT)/app-futexprobe.elf $(OUT)/app-lseekprobe.elf $(OUT)/app-sleepprobe.elf $(OUT)/app-gfxprobe.elf $(OUT)/app-waitprobe.elf $(OUT)/app-timerfdprobe.elf $(OUT)/app-dmesgprobe.elf $(OUT)/app-ptyprobe.elf $(OUT)/app-partprobe.elf $(OUT)/app-fatprobe.elf $(OUT)/app-page3probe.elf $(OUT)/app-beepprobe.elf $(OUT)/app-auditprobe.elf $(OUT)/app-fatlsprobe.elf $(OUT)/app-cryptprobe.elf $(OUT)/app-journalprobe.elf $(OUT)/app-userprobe.elf $(OUT)/app-compositorprobe.elf $(OUT)/app-dlprobe.elf $(OUT)/app-fatwrprobe.elf $(OUT)/app-fatbigprobe.elf $(OUT)/app-loginprobe.elf $(OUT)/app-cowfixprobe.elf $(OUT)/app-clonebrkprobe.elf $(OUT)/app-ondlprobe.elf $(OUT)/app-surfprobe.elf $(OUT)/app-epollprobe.elf $(OUT)/app-multidlprobe.elf $(OUT)/app-errnoprobe.elf $(OUT)/app-pqsigprobe.elf $(OUT)/app-wmprobe.elf $(OUT)/app-wmcheck.elf $(OUT)/app-nsprobe.elf $(OUT)/app-tlsprobe.elf
191191
APP_ELFS = $(APP_BASE_SHELL_ELF) $(COREUTILS_ELFS) $(OUT)/app-hello.elf $(OUT)/app-bigcprobe.elf
192192

193193
$(OUT)/app-base-shell.o: apps/base-shell/base_shell.asm | $(OUT)
@@ -1019,6 +1019,18 @@ test-dynlink-v1: image-go
10191019
test-dlopen-ondisk-v1: image-go
10201020
$(PYTHON) -m pytest tests/runtime/test_dlopen_ondisk_v1.py -v --junitxml=$(OUT)/pytest-dlopen-ondisk-v1.xml
10211021

1022+
test-pqsig-v1: image-go
1023+
$(PYTHON) -m pytest tests/runtime/test_pqsig_v1.py -v --junitxml=$(OUT)/pytest-pqsig-v1.xml
1024+
1025+
test-winsrv-v1: image-go
1026+
$(PYTHON) -m pytest tests/runtime/test_winsrv_v1.py -v --junitxml=$(OUT)/pytest-winsrv-v1.xml
1027+
1028+
test-pidns-v1: image-go
1029+
$(PYTHON) -m pytest tests/runtime/test_pidns_v1.py -v --junitxml=$(OUT)/pytest-pidns-v1.xml
1030+
1031+
test-tls-v1: image-go
1032+
$(PYTHON) -m pytest tests/runtime/test_tls_v1.py -v --junitxml=$(OUT)/pytest-tls-v1.xml
1033+
10221034
test-fatwrite-v1: image-go
10231035
$(PYTHON) -m pytest tests/runtime/test_fatwrite_v1.py -v --junitxml=$(OUT)/pytest-fatwrite-v1.xml
10241036

apps/c-bigprobe/bigprobe.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,50 @@ int main(void) {
308308
}
309309
printf("BIGC: ok sum=0x%x high=0x%x pages>2\n",
310310
(unsigned long)sum, (unsigned long)high);
311+
312+
/* rlibc v2/v3, exercised here on the dedicated disk (kept off the
313+
* size-tight shared app region): real free() with block reuse, and a
314+
* bidirectional buffered FILE* round-trip. */
315+
void *h1 = malloc(48);
316+
free(h1);
317+
void *h2 = malloc(48);
318+
void *h3 = malloc(48);
319+
printf("BIGC: heap reuse=%d distinct=%d\n", (long)(h2 == h1),
320+
(long)(h3 != h1 && h3 != h2));
321+
322+
FILE *wf = fopen("/data/bigc.txt", "w");
323+
if (wf) {
324+
fwrite("rlibc-stdio", 1, 11, wf);
325+
fclose(wf);
326+
FILE *rf = fopen("/data/bigc.txt", "r");
327+
if (rf) {
328+
char rb[16];
329+
size_t g = fread(rb, 1, sizeof(rb) - 1, rf);
330+
rb[g] = 0;
331+
int at_eof = feof(rf);
332+
fclose(rf);
333+
printf("BIGC: stdio rw[%d]=%s eof=%d\n", (long)g, rb, (long)at_eof);
334+
} else {
335+
puts("BIGC: stdio reopen err");
336+
}
337+
} else {
338+
puts("BIGC: stdio fopen err");
339+
}
340+
341+
/* rlibc v2 DISTINCT errno (Part V.11): two different failures, routed through
342+
* the libc open()/read() wrappers, now carry two different errno values
343+
* (ENOENT vs EBADF) instead of a single EIO -- the wrappers read sys_errno. */
344+
errno = 0;
345+
(void)open("/data/nope", O_RDONLY, 0); /* missing file -> ENOENT */
346+
int e_open = errno;
347+
errno = 0;
348+
char eb[1];
349+
(void)read(99, eb, 1); /* out-of-range fd -> EBADF */
350+
int e_read = errno;
351+
printf("BIGC: errno enoent=%d ebadf=%d distinct=%d\n",
352+
(long)(e_open == ENOENT), (long)(e_read == EBADF),
353+
(long)(e_open != e_read));
354+
311355
puts("BIGC: done");
312356
return 0;
313357
}

apps/coreutils/epollprobe.asm

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
; epollprobe: epoll readiness proof (sys_epoll, ABI v3.x id 55).
2+
;
3+
; Creates an epoll instance and a pipe, registers the pipe read end for
4+
; EPOLLIN, and proves LEVEL-TRIGGERED readiness: while the pipe is empty
5+
; epoll_wait reports 0 ready; after one byte is written it reports 1 ready,
6+
; with the returned fd == the read end and EPOLLIN (0x1) set in revents.
7+
;
8+
; sys_epoll: rdi=op, rsi=arg2, rdx=arg3, r10=arg4, eax=55.
9+
; op 1 create -> rax=epfd; op 2 ctl_add(ep,fd,events); op 3 wait(ep,buf,max).
10+
; pipe via sys_fs_ctl id 47 op 4 -> rax = rfd<<8 | wfd.
11+
12+
bits 64
13+
default rel
14+
15+
section .text
16+
global _start
17+
_start:
18+
; epoll_create -> r12 = epfd
19+
mov edi, 1
20+
mov eax, 55
21+
int 0x80
22+
cmp rax, -1
23+
je .fail
24+
mov r12, rax
25+
26+
; pipe() -> r13 = rfd, r14 = wfd
27+
mov edi, 4 ; FS_CTL_PIPE
28+
xor esi, esi
29+
xor edx, edx
30+
mov eax, 47
31+
int 0x80
32+
cmp rax, -1
33+
je .fail
34+
mov r13, rax
35+
shr r13, 8 ; rfd = pair >> 8
36+
mov r14, rax
37+
and r14, 0xFF ; wfd = pair & 0xFF
38+
39+
; epoll_ctl_add(epfd, rfd, EPOLLIN=1)
40+
mov edi, 2
41+
mov rsi, r12
42+
mov rdx, r13
43+
mov r10, 1
44+
mov eax, 55
45+
int 0x80
46+
cmp rax, -1
47+
je .fail
48+
49+
; epoll_wait #1: pipe empty -> 0 ready
50+
mov edi, 3
51+
mov rsi, r12
52+
lea rdx, [rel evbuf]
53+
mov r10, 4
54+
mov eax, 55
55+
int 0x80
56+
test rax, rax
57+
jnz .fail ; must be 0
58+
59+
; write one byte into the pipe write end
60+
mov rdi, r14
61+
lea rsi, [rel xbyte]
62+
mov edx, 1
63+
mov eax, 20 ; sys_write
64+
int 0x80
65+
cmp rax, 1
66+
jne .fail
67+
68+
; epoll_wait #2: now readable -> 1 ready
69+
mov edi, 3
70+
mov rsi, r12
71+
lea rdx, [rel evbuf]
72+
mov r10, 4
73+
mov eax, 55
74+
int 0x80
75+
cmp rax, 1
76+
jne .fail
77+
78+
; the ready record's fd must equal rfd and revents must have EPOLLIN
79+
mov eax, [rel evbuf] ; fd (i32, zero-extended)
80+
cmp rax, r13
81+
jne .fail
82+
movzx eax, word [rel evbuf + 4] ; revents (u16)
83+
and eax, 1
84+
jz .fail
85+
86+
lea rdi, [rel okmsg]
87+
mov esi, okmsg_len
88+
xor eax, eax
89+
int 0x80
90+
mov eax, 2
91+
int 0x80
92+
.fail:
93+
lea rdi, [rel failmsg]
94+
mov esi, failmsg_len
95+
xor eax, eax
96+
int 0x80
97+
mov eax, 2
98+
int 0x80
99+
100+
section .rodata
101+
okmsg: db "EPOLLPROBE: ready ok", 10
102+
okmsg_len equ $ - okmsg
103+
failmsg: db "EPOLLPROBE: FAIL", 10
104+
failmsg_len equ $ - failmsg
105+
xbyte: db "x"
106+
107+
section .bss
108+
evbuf: resb 32

apps/coreutils/errnoprobe.asm

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
; errnoprobe: distinct errno codes proof (sys_errno, ABI id 62).
2+
;
3+
; The kernel ABI returns a single -1 sentinel on failure; rlibc therefore mapped
4+
; EVERY error to EIO. Now well-defined failure paths stamp a per-task errno that
5+
; sys_errno (id 62) returns, so two DIFFERENT failures yield two DIFFERENT codes:
6+
; open("/data/nope") -> -1, errno = ENOENT (2) (no such file)
7+
; read(99, ...) -> -1, errno = EBADF (9) (bad file descriptor)
8+
; The probe fails unless both calls fail, each errno matches, and the two differ.
9+
;
10+
; sys_open: rdi=path, rsi=flags, rdx=mode (id 18). sys_read: rdi=fd, rsi=buf,
11+
; rdx=len (id 19). sys_errno: () -> code (id 62).
12+
13+
bits 64
14+
default rel
15+
16+
section .text
17+
global _start
18+
_start:
19+
; --- open a nonexistent /data file -> must fail with ENOENT ---
20+
lea rdi, [rel path]
21+
xor esi, esi ; O_RDONLY
22+
xor edx, edx ; mode 0
23+
mov eax, 18 ; SYS_OPEN
24+
int 0x80
25+
cmp rax, -1
26+
jne .fail ; it must NOT exist
27+
mov eax, 62 ; SYS_ERRNO
28+
int 0x80
29+
mov r12, rax ; r12 = errno after open (expect ENOENT=2)
30+
cmp r12, 2
31+
jne .fail
32+
33+
; --- read from a bad fd -> must fail with EBADF ---
34+
mov edi, 99 ; out-of-range fd
35+
lea rsi, [rel rbuf]
36+
mov edx, 1
37+
mov eax, 19 ; SYS_READ
38+
int 0x80
39+
cmp rax, -1
40+
jne .fail
41+
mov eax, 62 ; SYS_ERRNO
42+
int 0x80
43+
mov r13, rax ; r13 = errno after read (expect EBADF=9)
44+
cmp r13, 9
45+
jne .fail
46+
47+
; --- the two causes must be DISTINCT (not one collapsed EIO) ---
48+
cmp r12, r13
49+
je .fail
50+
51+
; --- a failure that does NOT stamp a code must leave errno CLEARED (0), not
52+
; the STALE EBADF from the read above. read(len>4096) returns -1 before any
53+
; errno stamp; the dispatch cleared errno on entry, so sys_errno must be 0. ---
54+
xor edi, edi
55+
lea rsi, [rel rbuf]
56+
mov edx, 5000 ; len > 4096 -> -1, un-stamped
57+
mov eax, 19 ; SYS_READ
58+
int 0x80
59+
cmp rax, -1
60+
jne .fail
61+
mov eax, 62 ; SYS_ERRNO
62+
int 0x80
63+
cmp rax, 0 ; must be cleared, NOT a stale 9 (EBADF)
64+
jne .fail
65+
66+
lea rdi, [rel okmsg]
67+
mov esi, okmsg_len
68+
xor eax, eax
69+
int 0x80
70+
mov eax, 2
71+
int 0x80
72+
.fail:
73+
lea rdi, [rel failmsg]
74+
mov esi, failmsg_len
75+
xor eax, eax
76+
int 0x80
77+
mov eax, 2
78+
int 0x80
79+
80+
section .data
81+
path: db "/data/nope", 0
82+
rbuf: times 8 db 0
83+
okmsg: db "ERRNO: distinct ok", 10
84+
okmsg_len equ $ - okmsg
85+
failmsg: db "ERRNO: FAIL", 10
86+
failmsg_len equ $ - failmsg

apps/coreutils/loginprobe.asm

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ _start:
2525
int 0x80
2626
cmp rax, -1
2727
jne .fail
28+
; the credential store /data/shadow (root-owned, owner-only, provisioned at
29+
; boot) must NOT be readable by this unprivileged (uid 100) app.
30+
lea rdi, [rel shadowpath]
31+
xor esi, esi
32+
xor edx, edx
33+
mov eax, 18 ; sys_open(path, O_RDONLY, 0)
34+
int 0x80
35+
cmp rax, -1
36+
jne .fail ; uid-100 read of the shadow store must be denied
37+
lea rdi, [rel shadowmsg]
38+
mov esi, shadowmsg_len
39+
xor eax, eax
40+
int 0x80
2841
; uid unchanged after a failed login
2942
mov edi, 3
3043
mov eax, 51
@@ -45,10 +58,48 @@ _start:
4558
int 0x80
4659
test rax, rax
4760
jnz .fail
61+
; root CAN read the shadow store -> it exists (proves it is a real file,
62+
; not merely absent in the denial above), then close it.
63+
lea rdi, [rel shadowpath]
64+
xor esi, esi
65+
xor edx, edx
66+
mov eax, 18
67+
int 0x80
68+
cmp rax, -1
69+
je .fail ; root must be able to open the credential store
70+
mov r13, rax
71+
mov rdi, r13
72+
mov eax, 21 ; sys_close
73+
int 0x80
4874
lea rdi, [rel okmsg]
4975
mov esi, okmsg_len
5076
xor eax, eax
5177
int 0x80
78+
; --- lockout proof: LOGIN_LOCKOUT (3) consecutive wrong root logins lock
79+
; the account, after which even the correct password is refused ---
80+
mov r12d, 3
81+
.lock_bad:
82+
mov edi, 5
83+
lea rsi, [rel uname]
84+
lea rdx, [rel badpw]
85+
mov eax, 51
86+
int 0x80
87+
cmp rax, -1
88+
jne .fail ; each wrong password must be denied
89+
dec r12d
90+
jnz .lock_bad
91+
; account locked now: the CORRECT password is also refused
92+
mov edi, 5
93+
lea rsi, [rel uname]
94+
lea rdx, [rel goodpw]
95+
mov eax, 51
96+
int 0x80
97+
cmp rax, -1
98+
jne .fail ; locked -> correct password still denied
99+
lea rdi, [rel lockmsg]
100+
mov esi, lockmsg_len
101+
xor eax, eax
102+
int 0x80
52103
mov eax, 2
53104
int 0x80
54105
.fail:
@@ -65,5 +116,10 @@ badpw: db "wrong", 0 ; NUL-terminated wrong password
65116
goodpw: db "toor", 0 ; NUL-terminated correct root password
66117
okmsg: db "LOGINPROBE: ok", 10
67118
okmsg_len equ $ - okmsg
119+
shadowpath: db "/data/shadow", 0
120+
shadowmsg: db "LOGINPROBE: shadow protected ok", 10
121+
shadowmsg_len equ $ - shadowmsg
122+
lockmsg: db "LOGINPROBE: lockout ok", 10
123+
lockmsg_len equ $ - lockmsg
68124
failmsg: db "LOGINPROBE: FAIL", 10
69125
failmsg_len equ $ - failmsg

0 commit comments

Comments
 (0)