diff --git a/defects/hurd/hurd-0001.md b/defects/hurd/hurd-0001.md new file mode 100644 index 000000000..cdd1ef9e8 --- /dev/null +++ b/defects/hurd/hurd-0001.md @@ -0,0 +1,31 @@ +# hurd-0001 — CWE-407: S_auth_makeauth — O(N×M×k) UID verification + +**Severity:** HIGH +**File:** `auth/auth.c` lines ~162–216 +**Function:** `S_auth_makeauth` +**CWE:** CWE-407 Algorithmic Complexity + +## Defect + +Four nested loops over `nauths × n{e,a}uids/n{e,a}gids`. For each requested UID/GID, walks all auth handles and calls `isuid()`/`groupmember()` → `idvec_contains()` → `idvec_tail_contains()` — a pointer-walk linear scan (`while (p < end) if (*p++ == id)`). + +```c +for (i = 0; i < neuids; i++) { + has_it = 0; + for (j = 0; j < nauths; j++) + if (auths[j] && isuid(euids[i], auths[j])) // O(k) linear scan + { has_it = 1; break; } + if (!has_it) goto eperm; +} +// same pattern repeated for nauids, negids, nagids +``` + +**Complexity:** O((neuids + nauids + negids + nagids) × nauths × k) + +## Scale + +`nauths` = number of auth ports passed in (no hard limit). `idvec` size (k) = supplementary UIDs/groups — easily 16–64. Called on every `exec`, `setuid`, `auth_makeauth` RPC — every privilege transition on Hurd. + +## Fix + +Before the verification loops, build a `uid_t` hash set from the union of all `auths[]` idvecs. Replace each `isuid()`/`groupmember()` call with O(1) hash lookup. Cost drops from O(N×M×k) to O(M×k) construction + O(N) lookup. diff --git a/defects/hurd/hurd-0002.md b/defects/hurd/hurd-0002.md new file mode 100644 index 000000000..c17fffc24 --- /dev/null +++ b/defects/hurd/hurd-0002.md @@ -0,0 +1,30 @@ +# hurd-0002 — CWE-407: ports_interrupt_rpc_on_notification — O(N) linked list scan per RPC + +**Severity:** MEDIUM +**File:** `libports/interrupt-on-notify.c` lines ~63–74 +**Function:** `ports_interrupt_rpc_on_notification` +**CWE:** CWE-407 Algorithmic Complexity + +## Defect + +Linear scan of `_ports_notifications` (global linked list) on every RPC notification setup, while holding `_ports_lock`. Second linear scan over `rpc->notifies` for duplicate check. + +```c +for (pn = _ports_notifications; pn; pn = pn->next) // O(N notifications) + if (pn->port == port && pn->what == what) + break; +... +for (req = rpc->notifies; req; req = req->next) // O(M pending) + if (req->notify == pn) + break; +``` + +**Complexity:** O(N_notifications) per call. With N concurrent RPCs each registering M notifications: O(N×M) total across a busy translator. + +## Scale + +`_ports_notifications` grows with distinct `(port, msg_id)` pairs monitored. In `ext2fs` with many simultaneous readers this list is long. Called per RPC setup in `libdiskfs` and `libtrivfs`. + +## Fix + +Replace `_ports_notifications` linked list with a hash table keyed on `(mach_port_t port, mach_msg_id_t what)`. O(1) lookup replaces O(N) scan. diff --git a/defects/hurd/hurd-0003.md b/defects/hurd/hurd-0003.md new file mode 100644 index 000000000..7bf70bf23 --- /dev/null +++ b/defects/hurd/hurd-0003.md @@ -0,0 +1,27 @@ +# hurd-0003 — CWE-407: ports_interrupt_notified_rpcs — O(N) scan on every port death + +**Severity:** MEDIUM +**File:** `libports/interrupt-notified-rpcs.c` lines ~31–46 +**Function:** `ports_interrupt_notified_rpcs` +**CWE:** CWE-407 Algorithmic Complexity + +## Defect + +Walks full `_ports_notifications` linked list on every dead-name notification (every time a watched port dies). Inner loop over `np->reqs` cancels pending RPCs. + +```c +for (np = _ports_notifications; np; np = np->next) // O(N notifications) + if (np->port == port && np->what == what) + { + struct rpc_notify *req; + for (req = np->reqs; req; req = req->next_req) // O(M requests) + if (req->pending) { req->pending--; hurd_thread_cancel(...); } + break; + } +``` + +**Complexity:** O(N_notifications) per dead-name event. Port deaths cascade — one process crash fires many dead-name events simultaneously. + +## Fix + +Same root cause as hurd-0002: hash `_ports_notifications` by `(port, what)`. Both hurd-0002 and hurd-0003 are fixed by the same data structure change. diff --git a/defects/hurd/hurd-0004.md b/defects/hurd/hurd-0004.md new file mode 100644 index 000000000..3c368ebbf --- /dev/null +++ b/defects/hurd/hurd-0004.md @@ -0,0 +1,32 @@ +# hurd-0004 — CWE-407: check_owner → check_uid — O(nuids²) double UID scan + +**Severity:** LOW-MEDIUM +**Files:** `proc/info.c` lines ~47–67, `proc/mgt.c` lines ~84–92 +**Functions:** `check_owner`, `check_uid` +**CWE:** CWE-407 Algorithmic Complexity + +## Defect + +`check_owner(proc1, proc2)` verifies proc1 holds every UID proc2 holds. Outer loop over proc2's UIDs, inner `check_uid()` linearly scans proc1's UIDs. + +```c +// check_owner — proc/info.c ~65 +for (size_t i = 0; i < proc2->p_id->i_nuids; i++) + if (!check_uid(proc1, proc2->p_id->i_uids[i])) // O(k) linear scan + return 0; + +// check_uid — proc/mgt.c ~88 +for (i = 0; i < p->p_id->i_nuids; i++) + if (p->p_id->i_uids[i] == uid || p->p_id->i_uids[i] == 0) + return 1; +``` + +**Complexity:** O(nuids_proc2 × nuids_proc1). With N = total UIDs per process, O(N²). + +## Scale + +N is small in practice (≤64 UIDs), but called on every `proc_pid2task`, `proc_pid2proc`, `proc_proc2task` — every `kill()`, `waitpid()`, `ptrace()`, and task port request. + +## Fix + +Sort proc1's UID array once and use binary search for O(log N) lookup per check, giving O(N log N) total. Or maintain a small bitset for O(1) lookup when UID range is bounded. diff --git a/defects/hurd/hurd-0005.md b/defects/hurd/hurd-0005.md new file mode 100644 index 000000000..c8a41dbbe --- /dev/null +++ b/defects/hurd/hurd-0005.md @@ -0,0 +1,33 @@ +# hurd-0005 — CWE-407: _ports_bucket_class_iterate — O(total_ports) global scan + +**Severity:** MEDIUM +**File:** `libports/bucket-iterate.c` lines ~41–80 +**Function:** `_ports_bucket_class_iterate` (called as `ports_class_iterate`) +**CWE:** CWE-407 Algorithmic Complexity + +## Defect + +When used as `ports_class_iterate`, scans the **global** `_ports_htable` (all ports in all buckets) and filters by class pointer. The source file itself carries: + +```c +/* This is obscenely ineffecient. ihash and ports need to cooperate + more closely to do it efficiently. */ +``` + +```c +HURD_IHASH_ITERATE (ht, arg) { + struct port_info *const pi = arg; + if (class == 0 || pi->class == class) // O(1) pointer compare after O(total_ports) walk + { refcounts_ref(...); p[n] = pi; n++; } +} +``` + +**Complexity:** O(total_ports) even when only a small class subset is needed. + +## Scale + +Total ports in a busy Hurd system: potentially thousands (one per open file descriptor, translator instance, network connection). `ports_class_iterate` is called during inhibit/resume operations — exactly when the system is under load. + +## Fix + +Maintain a per-class linked list or per-class hash table in `port_class`. `ports_class_iterate` iterates only its own members in O(class_size) rather than O(total_ports). This is what the source comment already asks for. diff --git a/defects/hurd/hurd-0006.md b/defects/hurd/hurd-0006.md new file mode 100644 index 000000000..4379b42c5 --- /dev/null +++ b/defects/hurd/hurd-0006.md @@ -0,0 +1,32 @@ +# hurd-0006 — CWE-407: idvec_merge_ids — O(N×k) deduplication scan + +**Severity:** MEDIUM +**File:** `libshouldbeinlibc/idvec.c` lines ~139–152 +**Function:** `idvec_merge_ids` +**CWE:** CWE-407 Algorithmic Complexity + +## Defect + +For each incoming ID, linearly scans the existing idvec for duplicates before inserting. + +```c +while (num-- > 0 && !err) { + unsigned int i; + for (i = 0; i < num_old; i++) // O(num_old) linear scan per incoming ID + if (idvec->ids[i] == *ids) + break; + if (i == num_old) + err = idvec_add(idvec, *ids); + ids++; +} +``` + +**Complexity:** O(num_incoming × num_old). Merging two idvecs of size k: O(k²). + +## Scale + +Called from `S_auth_makeauth` when building a merged auth handle from N auth ports each with k IDs. Total merge cost: O(nauths × k²). Compounds hurd-0001. + +## Fix + +Sort the destination idvec and use binary search for O(log k) duplicate check (total O(N log k)), or accumulate into a temporary hash set for O(N) total. diff --git a/whitepaper/outreach/hurd.md b/whitepaper/outreach/hurd.md index c99ad39b0..b2b922548 100644 --- a/whitepaper/outreach/hurd.md +++ b/whitepaper/outreach/hurd.md @@ -1,25 +1,52 @@ -# GNU Hurd — CWE-407 Scan Queue -**2026-04-04 · Scan in progress** +# GNU Hurd — CWE-407 Disclosure Brief +**2026-04-04 · 6 confirmed defects · Patches in progress** -## Target +## Finding -GNU Hurd microkernel OS — main Hurd translators (`hurd.git`), GNU Mach kernel (`gnumach.git`), and `libpthread`. +Six O(N²) / O(N×k) algorithmic complexity defects across GNU Hurd's auth server, `libports` notification infrastructure, `proc` server, and `libshouldbeinlibc` ID vector library. All are linear scan patterns in IPC-critical hot paths where hash or sorted structures should be used. -Primary language: C. Primary IPC mechanism: Mach ports. +## The Defects -## Scan Focus +**hurd-0001 (HIGH):** `auth/auth.c:162` — `S_auth_makeauth` -- `libports/` — port/capability lookup tables, bucket walks -- `libdiskfs/`, `libtrivfs/`, `libnetfs/` — server loop message dispatch -- Translator registration and lookup in core translators -- Any linked-list walk inside another linked-list walk -- `strcmp`-in-loop patterns over growing name tables +Four nested loops over `nauths × n{e,a}uids/n{e,a}gids`. Each `isuid()`/`groupmember()` call expands to `idvec_contains()` → `idvec_tail_contains()` — a pointer-walk linear scan over every UID in the auth handle. + +```c +for (i = 0; i < neuids; i++) { + for (j = 0; j < nauths; j++) + if (auths[j] && isuid(euids[i], auths[j])) // O(k) scan per call + { has_it = 1; break; } +} +// repeated for nauids, negids, nagids +``` + +O(N×M×k) total. Called on every `exec`, `setuid`, `auth_makeauth` RPC — the spine of Hurd's privilege model. Fix: build a `uid_t` hash set from the union of all `auths[]` idvecs before the verification loops; replace `isuid()` with O(1) hash lookup. + +**hurd-0002 (MEDIUM):** `libports/interrupt-on-notify.c:63` — `ports_interrupt_rpc_on_notification` + +Linear scan of `_ports_notifications` (global linked list) on every RPC notification registration while holding `_ports_lock`. O(N_notifications) per call. Called per RPC in `libdiskfs`/`libtrivfs`. Fix: hash `_ports_notifications` by `(port, what)`. + +**hurd-0003 (MEDIUM):** `libports/interrupt-notified-rpcs.c:31` — `ports_interrupt_notified_rpcs` + +Same `_ports_notifications` linear scan, fired on every dead-name event (every watched port death). Port deaths cascade. Same fix as hurd-0002 — shared root cause, single structural change fixes both. + +**hurd-0004 (LOW-MEDIUM):** `proc/info.c:65` + `proc/mgt.c:88` — `check_owner` → `check_uid` + +Double UID array linear scan on every `kill()`, `waitpid()`, `ptrace()`, task port request. O(nuids²). N is small in practice (≤64), but fires on every proc server IPC. Fix: sort + binary search. + +**hurd-0005 (MEDIUM):** `libports/bucket-iterate.c:41` — `_ports_bucket_class_iterate` + +Global `_ports_htable` scan filtered by class. Source comment reads: *"This is obscenely ineffecient. ihash and ports need to cooperate more closely to do it efficiently."* O(total_ports) even for a single-class iteration. Fix: per-class member list in `port_class`. + +**hurd-0006 (MEDIUM):** `libshouldbeinlibc/idvec.c:139` — `idvec_merge_ids` + +O(k) linear scan per incoming ID during idvec merge. O(k²) total when merging two idvecs of size k. Called from `S_auth_makeauth` — compounds hurd-0001. Fix: sort + binary search or temporary hash set. ## Contact GNU Hurd mailing list: `bug-hurd@gnu.org` -GNU Savannah: https://savannah.gnu.org/projects/hurd/ +Savannah project: https://savannah.gnu.org/projects/hurd/ ## Status -Scan queued 2026-04-04. Defect files pending. +Defects confirmed 2026-04-04. Patches in progress. hurd-0002 and hurd-0003 share a fix. hurd-0001 and hurd-0006 compound each other on the `auth_makeauth` path.