The Pedigree Project 0.1
scheduling-contract-test/permissions.c
1#define _GNU_SOURCE
2#include <unistd.h>
3
4#include "contract.h"
5#include <sys/fsuid.h>
6#include <sys/syscall.h>
7
8enum { OWNER_REAL = 51001, OWNER_EFFECTIVE = 51002, OWNER_SAVED = 51003, FOREIGN = 51004 };
10 atomic_uint ready;
11 pid_t tid;
12 int command, report;
13};
15 unsigned cpu;
16 int count, failed;
17};
18static void* target_entry(void* argument) {
19 struct target_state* state = argument;
20 if (sc_pin(0, sc_cpus[0]))
21 return (void*)1;
22 state->tid = (pid_t)syscall(SYS_gettid);
23 atomic_store_explicit(&state->ready, 1, memory_order_release);
24 for (;;) {
25 char command;
26 if (sc_read(state->command, &command, 1))
27 return (void*)1;
28 if (command == 'Q')
29 return NULL;
30 if (command != 'S')
31 return (void*)1;
32 unsigned node = UINT32_MAX;
33 cpu_set_t mask;
34 struct target_report result = {.cpu = UINT32_MAX};
35 result.failed = syscall(SYS_getcpu, &result.cpu, &node, NULL) || node ||
36 sched_getaffinity(0, sizeof(mask), &mask);
37 result.count = result.failed ? -1 : CPU_COUNT(&mask);
38 if (sc_write(state->report, &result, sizeof(result)))
39 return (void*)1;
40 }
41}
42static int target_child(int command, int report, void* argument) {
43 (void)argument;
44 if (setresuid(OWNER_REAL, OWNER_EFFECTIVE, OWNER_SAVED))
45 return 1;
46 int gate[2];
47 if (pipe(gate))
48 return 1;
49 struct target_state state = {.command = gate[0], .report = report};
50 pthread_t thread;
51 if (pthread_create(&thread, NULL, target_entry, &state))
52 return 1;
53 if (sc_wait(&state.ready, 1) || sc_write(report, &state.tid, sizeof(state.tid)))
54 _exit(2);
55 for (;;) {
56 char byte;
57 if (sc_read(command, &byte, 1) || (byte != 'S' && byte != 'Q') || sc_send(gate[1], byte))
58 _exit(3);
59 if (byte == 'Q')
60 break;
61 }
62 void* result;
63 int failed = pthread_join(thread, &result) || result;
64 close(gate[0]);
65 close(gate[1]);
66 return failed;
67}
69 pid_t target;
70 uid_t real, effective, filesystem;
71 int allowed, destination;
72};
73static int permission_child(int command, int report, void* argument) {
74 (void)command;
75 (void)report;
76 struct permission_case* test = argument;
77 int failed = 0;
78 CHECK(setresuid(test->real, test->effective, 0) == 0);
79 setfsuid(test->filesystem);
80 CHECK((uid_t)setfsuid((uid_t)-1) == test->filesystem);
81 CHECK(geteuid() == test->effective);
82 cpu_set_t before, after;
83 struct sched_param param = {.sched_priority = 0};
84 CHECK(sched_getaffinity(test->target, sizeof(before), &before) == 0);
85 CHECK(syscall(SYS_sched_getscheduler, test->target) == SCHED_OTHER);
86 CHECK(syscall(SYS_sched_getparam, test->target, &param) == 0 && !param.sched_priority);
87 errno = 0;
88 int result = sc_pin(test->target, test->destination);
89 CHECK(test->allowed ? result == 0 : result == -1 && errno == EPERM);
90 CHECK(sched_getaffinity(test->target, sizeof(after), &after) == 0);
91 CHECK(test->allowed ? sc_mask(test->target, test->destination) == 0 : CPU_EQUAL(&before, &after));
92 errno = 0;
93 long policy = syscall(SYS_sched_setparam, test->target, &param);
94 CHECK(test->allowed ? policy == 0 : policy == -1 && errno == EPERM);
95 errno = 0;
96 policy = syscall(SYS_sched_setscheduler, test->target, SCHED_OTHER, &param);
97 CHECK(test->allowed ? policy == 0 : policy == -1 && errno == EPERM);
98 CHECK(syscall(SYS_sched_getscheduler, test->target) == SCHED_OTHER);
99out:
100 return failed;
101}
102int sc_permissions(void) {
103 int failed = 0;
104 struct sc_peer target = SC_PEER_INITIALIZER, caller = SC_PEER_INITIALIZER;
105 CHECK(geteuid() == 0);
106 CHECK(sc_spawn(&target, target_child, NULL) == 0);
107 pid_t tid;
108 CHECK(sc_read(target.report, &tid, sizeof(tid)) == 0 && tid > 0 && tid != target.pid);
109 struct permission_case cases[] = {
110 {.real = FOREIGN, .effective = OWNER_REAL, .filesystem = FOREIGN, .allowed = 1},
111 {.real = FOREIGN, .effective = OWNER_EFFECTIVE, .filesystem = FOREIGN, .allowed = 1},
112 {.real = OWNER_REAL, .effective = FOREIGN, .filesystem = OWNER_REAL, .allowed = 0},
113 {.real = FOREIGN, .effective = OWNER_SAVED, .filesystem = FOREIGN, .allowed = 0},
114 {.real = FOREIGN, .effective = FOREIGN, .filesystem = 0, .allowed = 0},
115 {.real = FOREIGN, .effective = 0, .filesystem = FOREIGN, .allowed = 1}};
116 for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
117 cases[i].target = tid;
118 cases[i].destination = sc_cpus[sc_count - 1];
119 CHECK(sc_pin(tid, sc_cpus[0]) == 0);
120 CHECK(sc_spawn(&caller, permission_child, &cases[i]) == 0);
121 CHECK(sc_join(&caller) == 0);
122 CHECK(sc_send(target.command, 'S') == 0);
123 struct target_report result;
124 CHECK(sc_read(target.report, &result, sizeof(result)) == 0);
125 int expected = cases[i].allowed ? cases[i].destination : sc_cpus[0];
126 CHECK(!result.failed && result.count == 1 && result.cpu == (unsigned)expected);
127 // Targeting its worker TID must not mutate the leader's independent mask.
128 cpu_set_t leader;
129 CHECK(sched_getaffinity(target.pid, sizeof(leader), &leader) == 0 &&
130 CPU_EQUAL(&leader, &sc_allowed));
131 }
132 CHECK(sc_send(target.command, 'Q') == 0 && sc_join(&target) == 0);
133out:
134 sc_cleanup(&caller);
135 sc_cleanup(&target);
136 return failed;
137}