#include "guard.h" #include #include #include #include static unsigned checks; #define CHECK(x) do { if (!(x)) { fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #x); exit(1); } checks++; } while (0) static const char *uuid = "11111111-1111-4111-8111-111111111111"; static config_t allowed(void) { config_t c; defaults(&c); c.nzones = 1; strcpy(c.zones[0].uuid, uuid); strcpy(c.zones[0].alias, "fixture-web"); c.zones[0].policy = ALLOW; return c; } static process_t process(int pid) { process_t p; memset(&p, 0, sizeof (p)); p.pid = pid; p.ppid = 100; p.zid = 9; p.initpid = 99; p.nlwp = 1; p.zone_generation = 20; p.start_sec = 1000; p.start_nsec = 123; p.rss_kib = 1048576; strcpy(p.uuid, uuid); strcpy(p.name, "php-fpm"); return p; } static sample_t sample(double at, uint64_t free_pages, uint64_t scanner) { sample_t s; memset(&s, 0, sizeof (s)); s.at = at; s.free = free_pages; s.lots = 130875; s.des = 65437; s.min = 49077; s.scan = scanner; return s; } static void step(engine_t *e, config_t *c, double at, uint64_t free_pages, uint64_t scanner) { sample_t s = sample(at, free_pages, scanner); delta_t d = {0, 0, 0}; engine_step(e, c, &s, d, d, d); } static int parse(const char *text, config_t *c) { FILE *f = tmpfile(); int result; char error[128]; CHECK(f != NULL); CHECK(fputs(text, f) >= 0); rewind(f); result = config_read(f, c, error, sizeof (error)); fclose(f); return result; } static void configs(void) { config_t c; CHECK(parse("# defaults\n", &c) == 0 && !c.armed); CHECK(parse("ACTION_MODE dry-run\nprotect_process postgres\n", &c) == 0); CHECK(parse("ACTION_MODE armd\n", &c) != 0); CHECK(parse("ACTION_MODE armed\n", &c) != 0); CHECK(parse("ACTION_MODE dry-run\nACTION_MODE armed\n", &c) != 0); CHECK(parse("LOG_KEEP 0\n", &c) != 0); CHECK(parse("TERM_GRACE_SECONDS -1\n", &c) != 0); CHECK(parse("TERM_GRACE_SECONDS 1 garbage\n", &c) != 0); CHECK(parse("UNKNOWN foo\n", &c) != 0); CHECK(parse("PRE_SECONDS 20\nPRESSURE_SECONDS 10\n", &c) != 0); CHECK(parse("zone global global allow\n", &c) != 0); CHECK(parse("zone 11111111-1111-4111-8111-111111111111 web allow\nACTION_MODE armed\n", &c) == 0 && c.armed); CHECK(parse("zone 11111111-1111-4111-8111-111111111111 web allow\nzone 11111111-1111-4111-8111-111111111111 db protect\n", &c) != 0); CHECK(!uuid_valid("11111111-1111-4111-8111-11111111111g")); CHECK(!uuid_valid("00000000-0000-0000-0000-000000000000")); puts("PASS strict configuration, safe defaults, explicit UUID authorization"); } static void protections(void) { config_t c = allowed(); process_t p = process(123), q; CHECK(!protected_process(&c, &p)); q = p; q.zid = 0; CHECK(protected_process(&c, &q)); q = p; q.pid = 1; CHECK(protected_process(&c, &q)); q = p; q.pid = q.initpid; CHECK(protected_process(&c, &q)); q = p; q.initpid = 0; CHECK(protected_process(&c, &q)); q = p; q.zone_generation = 0; CHECK(protected_process(&c, &q)); q = p; q.nlwp = 0; CHECK(protected_process(&c, &q)); q = p; q.ppid = 0; CHECK(protected_process(&c, &q)); q = p; strcpy(q.name, "sshd"); CHECK(protected_process(&c, &q)); q = p; q.rss_kib = 100; CHECK(protected_process(&c, &q)); q = p; q.uuid[0] = '2'; CHECK(protected_process(&c, &q)); c.zones[0].policy = PROTECT; CHECK(protected_process(&c, &p)); c.zones[0].policy = OBSERVE; CHECK(protected_process(&c, &p)); c.zones[0].policy = ALLOW; strcpy(c.protected_names[c.nnames++], "php-fpm"); CHECK(protected_process(&c, &p)); c.nnames = 0; c.protected_pids[c.npids++] = p.pid; CHECK(protected_process(&c, &p)); q = p; CHECK(same_process(&p, &q)); q.start_nsec++; CHECK(!same_process(&p, &q)); q = p; q.zone_generation++; CHECK(!same_process(&p, &q)); q = p; q.uuid[0] = '2'; CHECK(!same_process(&p, &q)); puts("PASS mandatory protections, PID reuse and zone incarnation identity"); } static void histories(void) { history_t h = {0}; delta_t d; unsigned i; for (i = 0; i <= 70; i++) history_add(&h, i, 10000 - 10 * i); d = history_delta(&h, 70, 5); CHECK(d.valid && d.value == -50 && d.age == 5); d = history_delta(&h, 70, 15); CHECK(d.valid && d.value == -150); d = history_delta(&h, 70, 60); CHECK(d.valid && d.value == -600); CHECK(!history_delta(&h, 90, 5).valid); memset(&h, 0, sizeof (h)); history_add(&h, 0, 123); history_add(&h, 15, 100); CHECK(!history_delta(&h, 15, 5).valid); CHECK(history_delta(&h, 15, 15).value == -23); d.valid = 1; d.value = 100; CHECK(rank_score(1000, d) == 1200); CHECK(rank_score(UINT64_MAX - 10, d) == UINT64_MAX); d.value = -100; CHECK(rank_score(1000, d) == 1000); { uint64_t bytes = 0; CHECK(swap_physical_bytes_from_blocks(67024888, 58931824, &bytes) == 0); CHECK(bytes == UINT64_C(4143648768)); CHECK(swap_physical_bytes_from_blocks(10, 11, &bytes) != 0); CHECK(swap_physical_bytes_from_blocks(UINT64_MAX, 0, &bytes) != 0); } puts("PASS timestamped 5/15/60s windows, missing data, signed growth, overflow"); } static void states(void) { config_t c = allowed(); engine_t e; unsigned i; engine_init(&e); for (i = 0; i < 10; i++) step(&e, &c, i, 425950, 0); CHECK(e.state == NORMAL && !e.danger); for (i = 10; i <= 12; i++) step(&e, &c, i, 100000, 1); CHECK(e.state == NORMAL); step(&e, &c, 13, 100000, 1); CHECK(e.state == PRE_PRESSURE); for (i = 14; i <= 20; i++) step(&e, &c, i, 100000, 1); CHECK(e.state == PRESSURE && !e.danger); step(&e, &c, 21, 60000, 1); step(&e, &c, 22, 59000, 1); CHECK(!e.danger); step(&e, &c, 23, 58000, 1); CHECK(e.state == CRITICAL && e.danger); step(&e, &c, 24, 140000, 0); CHECK(e.state == RECOVERY && !e.danger); for (i = 25; i <= 40; i++) step(&e, &c, i, 140000, 0); CHECK(e.state == RECOVERY); /* below 10% margin */ for (i = 41; i <= 56; i++) step(&e, &c, i, 150000, 0); CHECK(e.state == NORMAL); step(&e, &c, 57, 40000, 0); CHECK(e.state == EMERGENCY && e.danger && !e.floor); step(&e, &c, 58, 20000, 0); CHECK(e.floor); engine_init(&e); for (i = 0; i < 20; i++) step(&e, &c, i, 60000, 0); CHECK(!e.danger); /* low but stable, no scanner */ engine_init(&e); step(&e, &c, 0, 60000, 1); step(&e, &c, 20, 59000, 1); CHECK(!e.danger); /* gaps do not count as sustained samples */ engine_gap(&e); step(&e, &c, 21, 58000, 1); CHECK(!e.danger); step(&e, &c, 22, 57000, 1); step(&e, &c, 23, 56000, 1); CHECK(e.danger); { sample_t s = sample(1, 60000, 0); delta_t none = {0, 0, 0}, rising = {1, 5, 4096}; engine_init(&e); engine_step(&e, &c, &s, none, rising, none); CHECK(!e.danger && e.critical_since < 0); /* physical swap alone is inert */ engine_init(&e); engine_step(&e, &c, &s, none, none, rising); CHECK(!e.danger && e.critical_since < 0); /* pageout alone is inert */ s.free = 150000; engine_init(&e); engine_step(&e, &c, &s, none, rising, rising); CHECK(e.state == NORMAL && e.low_since < 0); /* corroboration needs low memory */ s.free = 60000; engine_init(&e); for (i = 0; i <= 3; i++) { s.at = i; engine_step(&e, &c, &s, none, rising, rising); } CHECK(e.state == CRITICAL && e.danger); } puts("PASS all six states, corroboration, hysteresis, recovery and sampling gaps"); } static void actions(void) { config_t c = allowed(); engine_t e; controller_t a = {0}; process_t p = process(123); sample_t s; plan_t plan; unsigned i; engine_init(&e); e.state = CRITICAL; e.danger = 1; s = sample(100, 60000, 1); plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == TERM); action_commit(&a, &c, &s, &plan); CHECK(a.has_pending && a.nvictims == 1); printf("SIMULATED t=100 state=CRITICAL UUID=%s pid=123 WOULD_ACTION=SIGTERM\n", uuid); s.at = 104; plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == NONE); s.at = 105; plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == KILL); action_commit(&a, &c, &s, &plan); CHECK(!a.has_pending && a.nvictims == 1); puts("SIMULATED t=105 state=CRITICAL pid=123 WOULD_ACTION=SIGKILL reason=memory_not_recovered"); p.pid++; s.at = 106; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE); e.state = EMERGENCY; s.free = 40000; plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == TERM); action_commit(&a, &c, &s, &plan); s.at = 106.5; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE); s.at = 107; CHECK(action_plan(&a, &c, &e, &s, &p).action == KILL); s.free = 20000; e.floor = 1; plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == KILL); action_commit(&a, &c, &s, &plan); p.pid++; s.at++; plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == KILL); action_commit(&a, &c, &s, &plan); puts("SIMULATED state=EMERGENCY floor=1 WOULD_ACTION=SIGKILL reason=floor"); p.pid++; s.at++; plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == KILL); action_commit(&a, &c, &s, &plan); p.pid++; s.at++; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE); /* even at floor */ e.state = NORMAL; e.danger = 0; s.free = 160000; (void)action_plan(&a, &c, &e, &s, &p); CHECK(a.nvictims == 0); e.state = EMERGENCY; e.danger = 1; s.free = 20000; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE); /* minute budget persists */ s.at = 180; CHECK(action_plan(&a, &c, &e, &s, &p).action == KILL); memset(&a, 0, sizeof (a)); e.floor = 0; e.state = CRITICAL; s.free = 60000; plan = action_plan(&a, &c, &e, &s, &p); action_commit(&a, &c, &s, &plan); s.free = 140000; e.danger = 0; e.state = RECOVERY; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE && !a.has_pending); for (i = 0; i < 2; i++) { c.armed = (int)i; memset(&a, 0, sizeof (a)); e.danger = 1; e.state = EMERGENCY; e.floor = 1; s.free = 1000; p.zid = 0; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE); p.zid = 9; c.zones[0].policy = PROTECT; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE); c.zones[0].policy = ALLOW; } puts("PASS TERM/KILL sequencing, emergency grace, floor, cooldown, budgets and no-action protections"); } int main(void) { configs(); protections(); histories(); states(); actions(); printf("PASS %u checks; no native backend linked, no signals sent\n", checks); return 0; }