diff --git a/grade-lab-syscall b/grade-lab-syscall index a67a38b36eaad78114e94c0ba38445a7b86e1874..4747663f3c585310cc8789a78974b65e413871ff 100755 --- a/grade-lab-syscall +++ b/grade-lab-syscall @@ -8,30 +8,57 @@ r = Runner(save("xv6.out")) @test(40, "exit test") def test_exit_test(): - subprocess.run(["make", "clean"]) + result = subprocess.run(["make", "clean"]) + assert_equal(result.returncode, 0, "make clean failed") r.run_qemu(shell_script([ 'exittest' ]), make_args=["LAB_SYSCALL_TEST=on"]) - r.match('proc \\d+ exit, parent pid \\d+, name \\w+, state \\w+') - r.match('proc \\d+ exit, child 0, pid \\d+, name \\w+, state \\w+') - r.match('proc \\d+ exit, child 1, pid \\d+, name \\w+, state \\w+') - r.match('proc \\d+ exit, child 2, pid \\d+, name \\w+, state \\w+') + output = "\n".join(r.qemu.output.splitlines()) + states = r'(?:sleep|run|runble)' + parent_re = re.compile( + rf'^\[INFO\] proc (\d+) exit, parent pid (\d+), name (\w+), state ({states})$', + re.MULTILINE) + child_re = re.compile( + rf'^\[INFO\] proc (\d+) exit, child ([0-2]), pid (\d+), name (\w+), state ({states})$', + re.MULTILINE) + + parents = parent_re.findall(output) + children = child_re.findall(output) + candidates = [] + for exit_pid in {item[0] for item in children}: + group = [item for item in children if item[0] == exit_pid] + if {int(item[1]) for item in group} == {0, 1, 2}: + candidates.append((exit_pid, group)) + + assert_equal(len(candidates), 1, + "expected one exiting process with child records 0, 1, and 2") + exit_pid, group = candidates[0] + parent = [item for item in parents if item[0] == exit_pid] + assert_equal(len(parent), 1, "missing parent record for the exiting test process") + assert_equal(parent[0][2], "sh", "the test process parent should be sh") + + group.sort(key=lambda item: int(item[1])) + assert_equal([item[3] for item in group], ["child0", "child1", "child2"], + "child record names or numbering are wrong") + assert_equal(len({item[2] for item in group}), 3, "child pids must be distinct") + r.match(no=[r'^exit test FAIL']) @test(40, "wait test") def test_wait_test(): r.run_qemu(shell_script([ 'waittest' ]), make_args=["LAB_SYSCALL_TEST=on"]) - r.match('wait test OK') + r.match(r'^wait test OK$', no=[r'^wait test FAIL']) @test(20, "yield test") def test_yield_test(): r.run_qemu(shell_script([ 'yieldtest' ]), make_args=["CPUS=1", "LAB_SYSCALL_TEST=on"]) - r.match("parent yield") + r.match(r"^parent yield$", r"^yield test OK$", no=[r"^yield test FAIL"]) + output = "\n".join(r.qemu.output.splitlines()) - match = re.search(r'Save the context of the process to the memory region from address (0x[0-9a-fA-F]+) to (0x[0-9a-fA-F]+)', r.qemu.output, re.MULTILINE) + match = re.search(r'Save the context of the process to the memory region from address (0x[0-9a-fA-F]+) to (0x[0-9a-fA-F]+)', output, re.MULTILINE) assert match is not None, "can't find this sentence: Save the context of the process to the memory region from address (0x[0-9a-fA-F]+) to (0x[0-9a-fA-F]+)" addr1 = int(match.group(1), 16) addr2 = int(match.group(2), 16) @@ -41,24 +68,47 @@ def test_yield_test(): raise AssertionError(msg) assert_custom_equal(difference, 112, "context address saved region is wrong") - matches = re.findall("Current running process pid is \\w+ and user pc is \\w+", r.qemu.output, re.MULTILINE) - sepc = [re.search(r'0x[0-9a-fA-F]+', item).group() for item in matches][0] + match = re.search( + r'^Current running process pid is (\d+) and user pc is (0x[0-9a-fA-F]+)$', + output, re.MULTILINE) + assert match is not None, "missing current process pid/user pc line" + sepc = match.group(2) toolprefix = test_yield_test.options.toolprefix - result = subprocess.run([f"{toolprefix}addr2line", sepc, "-f", "-s", "-e", "user/_yieldtest"], stdout=subprocess.PIPE, check=True) - out = result.stdout.decode("utf-8") - assert_equal(out[:6], "yield\n") - match = re.search(r'Next runnable process pid is (\d+) and user pc is (0x[0-9a-fA-F]+)', r.qemu.output, re.MULTILINE) + def symbol_for(pc): + result = subprocess.run( + [f"{toolprefix}addr2line", pc, "-f", "-s", "-e", "user/_yieldtest"], + stdout=subprocess.PIPE) + assert_equal(result.returncode, 0, "addr2line failed") + lines = result.stdout.decode("utf-8").splitlines() + assert lines, f"addr2line returned no symbol for {pc}" + return lines[0] + + assert_equal(symbol_for(sepc), "yield") + + match = re.search(r'Next runnable process pid is (\d+) and user pc is (0x[0-9a-fA-F]+)', output, re.MULTILINE) assert match is not None, "can't find this sentence: Next runnable process pid is (\\d+) and user pc is (0x[0-9a-fA-F]+)" - pid = match.group(1) - sepc = match.group(2) - matches = re.findall("Next runnable process pid is \\w+ and user pc is \\w+", r.qemu.output, re.MULTILINE) - result = subprocess.run([f"{toolprefix}addr2line", sepc, "-f", "-s", "-e", "user/_yieldtest"], stdout=subprocess.PIPE, check=True) - out = result.stdout.decode("utf-8") - assert_equal(out[:5], "fork\n") - r.match(f".*?Child with PID {pid} begins to run") - r.match(".*?Child with PID 5 begins to run") - r.match(".*?Child with PID 6 begins to run") - r.match(".*?parent yield finished") + pid = match.group(1) + sepc = match.group(2) + assert_equal(symbol_for(sepc), "fork") + + child_pids = re.findall(r'^Child with PID (\d+) begins to run$', output, re.MULTILINE) + assert_equal(len(child_pids), 3, "yieldtest should run exactly three children") + assert_equal(len(set(child_pids)), 3, "yieldtest child pids must be distinct") + lines = output.splitlines() + parent_yield_line = lines.index("parent yield") + parent_finished_line = lines.index("parent yield finished") + children_after_yield = [ + (index, re.match(r'^Child with PID (\d+) begins to run$', line).group(1)) + for index, line in enumerate(lines) + if index > parent_yield_line + and re.match(r'^Child with PID (\d+) begins to run$', line) + ] + assert children_after_yield, "no child ran after the parent called yield" + assert_equal(children_after_yield[0][1], pid, + "the reported next runnable process was not scheduled next") + assert all(index < parent_finished_line for index, _ in children_after_yield), \ + "the parent resumed before all runnable test children" + r.match(r"^parent yield finished$") run_tests(exit_on_failure=False) diff --git a/grade-lab-syscall-extra b/grade-lab-syscall-extra index 2705dc0c1d65b60435f13e259522ae52f46271f3..dd5c5b86a6cd8e41870ab72ed5b3c760b3f2b9bb 100755 --- a/grade-lab-syscall-extra +++ b/grade-lab-syscall-extra @@ -1,5 +1,6 @@ #!/usr/bin/env python +import subprocess from gradelib import * r = Runner(save("xv6.out")) @@ -7,10 +8,20 @@ r = Runner(save("xv6.out")) @test(20, "seccomp test") def test_seccomp_test(): + result = subprocess.run(["make", "clean"]) + assert_equal(result.returncode, 0, "make clean failed") r.run_qemu(shell_script([ 'seccomptest' ]), make_args=["CPUS=1", "LAB_SYSCALL_TEST=on"]) - r.match("seccomp test OK") + r.match( + r"^PASS: zero mask blocks ordinary syscalls$", + r"^PASS: seccomp_getlog\(\) bypasses allowlist$", + r"^PASS: audit log contains blocked syscalls in order$", + r"^PASS: child inherits allowlist and its own audit log$", + r"^PASS: child inherits max-children limit$", + r"^PASS: fourth fork\(\) blocked by child limit$", + r"^seccomp test OK$", + no=[r"^FAIL:", r"^seccomp test FAIL$"]) run_tests(exit_on_failure=False) diff --git a/kernel/printf.c b/kernel/printf.c index be48928fce5b1b3f42c716091103cfe737fd718f..4827c731cda26044ec93b92226cfccb74f120928 100644 --- a/kernel/printf.c +++ b/kernel/printf.c @@ -56,9 +56,9 @@ void info_with_color(char *fmt, ...) { va_list ap; int i, c, locking; char *s; + static const char *info = "[INFO] "; #ifndef LAB_SYSCALL_TEST static const char *color = "\033[0;36m"; - static const char *info = "[INFO] "; static const char *rst_str = "\033[0m"; #endif @@ -71,10 +71,10 @@ void info_with_color(char *fmt, ...) { for (i = 0; color[i] != 0; i++) { consputc(color[i]); } +#endif for (i = 0; info[i] != 0; i++) { consputc(info[i]); } -#endif va_start(ap, fmt); for (i = 0; (c = fmt[i] & 0xff) != 0; i++) { diff --git a/user/exittest.c b/user/exittest.c index f1f820ee5990f96468f10fa8b28dadf3680c6089..4cc7de5ac5fd32105b83e02122a4b5de0a0714f6 100644 --- a/user/exittest.c +++ b/user/exittest.c @@ -9,6 +9,10 @@ void exittest(void) { for (int i = 0; i < 3; i++) { int pid = fork(); + if (pid < 0) { + printf("exit test FAIL: fork failed\n"); + exit(1); + } if (pid == 0) { // 给每个子进程起一个不同的名字 char name[16] = "child"; @@ -29,4 +33,4 @@ void exittest(void) { int main(int argc, char *argv[]) { exittest(); exit(0); -} \ No newline at end of file +} diff --git a/user/seccomptest.c b/user/seccomptest.c index aec0012a5c415e07bfd3362a06b8648c6edbe5e4..952481aa469485f1ed45fab309904c3ed67b9511 100644 --- a/user/seccomptest.c +++ b/user/seccomptest.c @@ -1,5 +1,6 @@ #include "kernel/types.h" #include "kernel/stat.h" +#include "kernel/fcntl.h" #include "user/user.h" #define SECCOMP_SETMASK 0 @@ -24,18 +25,42 @@ main(int argc, char *argv[]) // === Task 1: Syscall Allowlist === printf("\n-- Task 1: Syscall Allowlist --\n"); - // Only allow: write(16), exit(2), getpid(11), sbrk(12), close(21), - // seccomp_ctl(23), seccomp_getlog(24) - uint64 mask = (1UL << 2) | (1UL << 11) | (1UL << 12) | (1UL << 16) - | (1UL << 21) | (1UL << 23) | (1UL << 24); - seccomp_ctl(SECCOMP_SETMASK, mask); + char *testfile = "seccomp-test-file"; + int setup_fd = open(testfile, O_CREATE | O_RDWR); + check(setup_fd >= 0, "create file used by blocked-open test"); + if (setup_fd >= 0) { + close(setup_fd); + } + + // A zero mask must block every ordinary syscall. seccomp_ctl(23) remains + // available so that the process can restore a usable mask. + int empty_mask_ret = seccomp_ctl(SECCOMP_SETMASK, 0); + int blocked_pid = getpid(); + + // Allow ordinary syscalls needed by this test. seccomp_ctl(23) and + // seccomp_getlog(24) are deliberately absent: they must bypass the mask. + uint64 mask = (1UL << 2) | (1UL << 3) | (1UL << 12) | (1UL << 16) + | (1UL << 21); + int restore_mask_ret = seccomp_ctl(SECCOMP_SETMASK, mask); + check(empty_mask_ret >= 0, "seccomp_ctl() accepts an empty allowlist"); + check(restore_mask_ret >= 0, "seccomp_ctl() sets allowlist"); + check(blocked_pid < 0, "zero mask blocks ordinary syscalls"); // Fork should be blocked (SYS_fork = 1, not in mask) int pid = fork(); + if (pid == 0) { + exit(1); + } + if (pid > 0) { + wait(0, 0); + } check(pid < 0, "fork() blocked by seccomp"); // Open should be blocked (SYS_open = 15, not in mask) - int fd = open("test.txt", 0); + int fd = open(testfile, O_RDONLY); + if (fd >= 0) { + close(fd); + } check(fd < 0, "open() blocked by seccomp"); // Write should still work (SYS_write = 16, in mask) @@ -46,26 +71,78 @@ main(int argc, char *argv[]) uint64 logbuf[32]; int loglen = 32; - seccomp_getlog(logbuf, &loglen); + int ret = seccomp_getlog(logbuf, &loglen); + check(ret >= 0, "seccomp_getlog() bypasses allowlist"); - if (loglen > 0) { + if (ret >= 0 && loglen > 0) { printf(" Audit log has %d blocked syscall(s):\n", loglen); for (int i = 0; i < loglen; i++) { printf(" [%d] syscall %d blocked\n", i, (int)logbuf[i]); } - check(loglen >= 1, "audit log contains blocked syscalls"); + check(loglen == 3 && logbuf[0] == 11 && logbuf[1] == 1 && logbuf[2] == 15, + "audit log contains blocked syscalls in order"); } else { check(0, "audit log should not be empty"); } + // The allowlist itself must be inherited across fork. + uint64 inherit_mask = (1UL << 1) | (1UL << 2) | (1UL << 3) + | (1UL << 12) | (1UL << 16) | (1UL << 21); + check(seccomp_ctl(SECCOMP_SETMASK, inherit_mask) >= 0, + "set allowlist for inheritance test"); + pid = fork(); + if (pid == 0) { + uint64 child_log[4]; + int child_loglen = 4; + int child_pid = getpid(); + int logret = seccomp_getlog(child_log, &child_loglen); + int inherited = child_pid < 0 && logret >= 0 && child_loglen == 1 + && child_log[0] == 11; + exit(inherited ? 0 : 1); + } + int status = -1; + int waited = pid > 0 ? wait(&status, 0) : -1; + check(pid > 0 && waited == pid && status == 0, + "child inherits allowlist and its own audit log"); + // === Task 3: Process Resource Limits === printf("\n-- Task 3: Process Resource Limits --\n"); // Allow every syscall for the resource-limit test. - seccomp_ctl(SECCOMP_SETMASK, ~0UL); + check(seccomp_ctl(SECCOMP_SETMASK, ~0UL) >= 0, + "seccomp_ctl() bypasses allowlist"); - // Set max children to 2 - seccomp_ctl(SECCOMP_MAXCHILD, 2); + // A child must inherit the configured limit, but starts with no children. + check(seccomp_ctl(SECCOMP_MAXCHILD, 1) >= 0, + "set child limit for inheritance test"); + pid = fork(); + if (pid == 0) { + int grandchild = fork(); + if (grandchild == 0) { + sleep(5); + exit(0); + } + int blocked = fork(); + if (blocked == 0) { + exit(0); + } + int inherited = grandchild > 0 && blocked < 0; + if (grandchild > 0) { + wait(0, 0); + } + if (blocked > 0) { + wait(0, 0); + } + exit(inherited ? 0 : 1); + } + status = -1; + waited = pid > 0 ? wait(&status, 0) : -1; + check(pid > 0 && waited == pid && status == 0, + "child inherits max-children limit"); + + // Set max children to 2. + check(seccomp_ctl(SECCOMP_MAXCHILD, 2) >= 0, + "set max-children limit"); // First fork should succeed pid = fork(); @@ -74,11 +151,10 @@ main(int argc, char *argv[]) } check(pid > 0, "first fork() succeeds (within limit)"); - // Wait for first child so child_count resets - wait(0, 0); + // Reaping this child must decrement child_count. + check(wait(0, 0) == pid, "wait() reaps first child"); - // Fork two more children to test that limit works - seccomp_ctl(SECCOMP_MAXCHILD, 2); + // Fork two more children without resetting the limit or child_count. pid = fork(); if (pid == 0) { @@ -96,11 +172,18 @@ main(int argc, char *argv[]) // Now child_count = 2, max_children = 2, next fork should fail pid = fork(); + if (pid == 0) { + exit(1); + } check(pid < 0, "fourth fork() blocked by child limit"); + if (pid > 0) { + check(wait(0, 0) > 0, "wait() reaps unexpected fourth child"); + } // Wait for remaining children - wait(0, 0); - wait(0, 0); + check(wait(0, 0) > 0, "wait() reaps second child"); + check(wait(0, 0) > 0, "wait() reaps third child"); + unlink(testfile); printf("\n--- %s ---\n", passed ? "seccomp test OK" : "seccomp test FAIL"); @@ -109,5 +192,5 @@ main(int argc, char *argv[]) } else { printf("seccomp test FAIL\n"); } - exit(0); + exit(passed ? 0 : 1); } diff --git a/user/waittest.c b/user/waittest.c index a193ca37f5b240995aee73d2232df3249e1d249c..c989e02fd0f5b04e653a65868fec8c91ab158f48 100644 --- a/user/waittest.c +++ b/user/waittest.c @@ -4,27 +4,67 @@ const int nonblock = 1; +void fail(char *msg) { + printf("wait test FAIL: %s\n", msg); + exit(1); +} + void waittest(void) { printf("wait test\n"); + int ready[2]; + if (pipe(ready) < 0) { + fail("pipe failed"); + } + int pid = fork(); + if (pid < 0) { + fail("fork failed"); + } if (pid == 0) { - // 这里睡眠足够长的时间,因此后面父进程进行非阻塞wait时该子进程一定还没有退出 + close(ready[0]); + write(ready[1], "r", 1); + close(ready[1]); + // Tell the parent that this child is alive before it tries non-blocking wait. sleep(10); - exit(0); + exit(37); + } + + close(ready[1]); + char byte; + if (read(ready[0], &byte, 1) != 1) { + fail("child readiness handshake failed"); + } + close(ready[0]); + + if (wait(0, nonblock) != -1) { + fail("non-blocking wait reaped a live child"); } - for (int i = 0; i < 3; i++) { - if (wait(0, nonblock) < 0) { - printf("no child exited yet, round %d\n", i); - } else { - printf("wait shouldn't block!\n"); - printf("wait test FAIL\n"); - exit(1); - } + int status = -1; + int child_pid = wait(&status, 0); + if (child_pid != pid || status != 37) { + fail("blocking wait returned the wrong pid or status"); + } + + // A non-blocking wait must also reap a child that is already a zombie. + pid = fork(); + if (pid < 0) { + fail("second fork failed"); + } + if (pid == 0) { + exit(23); + } + + status = -1; + child_pid = -1; + for (int i = 0; i < 20 && child_pid < 0; i++) { + sleep(1); + child_pid = wait(&status, nonblock); + } + if (child_pid != pid || status != 23) { + fail("non-blocking wait did not reap a zombie child"); } - int child_pid = wait(0, 0); - printf("child exited, pid %d\n", child_pid); printf("wait test OK\n"); } @@ -32,4 +72,4 @@ void waittest(void) { int main(int argc, char *argv[]) { waittest(); exit(0); -} \ No newline at end of file +} diff --git a/user/yieldtest.c b/user/yieldtest.c index d7e958c42da349fa441d8b00d062673efad63ee7..66b2a5684f161412644b34b439197eff405bf68a 100644 --- a/user/yieldtest.c +++ b/user/yieldtest.c @@ -7,10 +7,13 @@ void yieldtest(void) { for (int i = 0; i < 3; i++) { int pid = fork(); + if (pid < 0) { + printf("yield test FAIL: fork failed\n"); + exit(1); + } if (pid == 0) { printf("Child with PID %d begins to run\n", getpid()); exit(0); - } else { } } printf("parent yield\n"); @@ -19,11 +22,15 @@ void yieldtest(void) { yield(); printf("parent yield finished\n"); for (int i = 0; i < 3; i++) { - wait(0, 0); + if (wait(0, 0) < 0) { + printf("yield test FAIL: wait failed\n"); + exit(1); + } } + printf("yield test OK\n"); } int main(int argc, char *argv[]) { yieldtest(); exit(0); -} \ No newline at end of file +}