From 360b7abb54f66fd50518ab873162929cf5f1d560 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sat, 4 Apr 2026 20:29:35 -0400 Subject: [PATCH] hurd: add patches for hurd-0001/0006 (auth) and hurd-0002/0003 (notify htable) --- ...1-hurd-0006-fix-O-N-M-k-auth-verify-.patch | 248 ++++++++++++++ ...2-hurd-0003-replace-_ports_notificat.patch | 303 ++++++++++++++++++ 2 files changed, 551 insertions(+) create mode 100644 defects/hurd/patch/0001-CWE-407-hurd-0001-hurd-0006-fix-O-N-M-k-auth-verify-.patch create mode 100644 defects/hurd/patch/0002-CWE-407-hurd-0002-hurd-0003-replace-_ports_notificat.patch diff --git a/defects/hurd/patch/0001-CWE-407-hurd-0001-hurd-0006-fix-O-N-M-k-auth-verify-.patch b/defects/hurd/patch/0001-CWE-407-hurd-0001-hurd-0006-fix-O-N-M-k-auth-verify-.patch new file mode 100644 index 000000000..125966e56 --- /dev/null +++ b/defects/hurd/patch/0001-CWE-407-hurd-0001-hurd-0006-fix-O-N-M-k-auth-verify-.patch @@ -0,0 +1,248 @@ +From a56d5fc886eaea781d1033bf5dc1cd94e4dad4a0 Mon Sep 17 00:00:00 2001 +From: "russell@unturf.com" +Date: Sat, 4 Apr 2026 20:29:09 -0400 +Subject: [PATCH 1/2] =?UTF-8?q?CWE-407=20(hurd-0001,=20hurd-0006):=20fix?= + =?UTF-8?q?=20O(N=C3=97M=C3=97k)=20auth=20verify=20and=20O(k=C2=B2)=20idve?= + =?UTF-8?q?c=20merge?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +hurd-0001 (auth/auth.c): S_auth_makeauth scanned every auth handle for every +requested UID/GID — O((neuids+nauids+negids+nagids) × nauths × k) where k is +idvec size. Fixed by building union sets of permitted UIDs/GIDs from all auth +handles once before the verification loops. Verification is now a single pass +over the union set — O(nauths×k) build + O(N×k) verify. + +hurd-0006 (libshouldbeinlibc/idvec.c): idvec_merge_ids checked each incoming +id by linear scan — O(num × num_old). Fixed by sorting a copy of the existing +ids and using binary search — O(num_old×log(num_old) + num×log(num_old)). +Called by S_auth_makeauth on every exec/setuid. +--- + auth/auth.c | 98 ++++++++++++++++----------------------- + libshouldbeinlibc/idvec.c | 51 ++++++++++++++++---- + 2 files changed, 84 insertions(+), 65 deletions(-) + +diff --git a/auth/auth.c b/auth/auth.c +index d5ef5876..bf75bfe0 100644 +--- a/auth/auth.c ++++ b/auth/auth.c +@@ -126,6 +126,7 @@ S_auth_makeauth (struct authhandle *auth, + int hasroot = 0; + error_t err; + size_t i, j; ++ struct idvec all_uids, all_gids; + + if (!auth) + return EOPNOTSUPP; +@@ -141,78 +142,50 @@ S_auth_makeauth (struct authhandle *auth, + /* Verify that the union of the handles passed in either contains euid 0 + (root), or contains all the requested ids. */ + +-#define isuid(uid, auth) \ +- (idvec_contains (&(auth)->euids, uid) \ +- || idvec_contains (&(auth)->auids, uid)) +-#define groupmember(gid, auth) \ +- (idvec_contains (&(auth)->egids, gid) \ +- || idvec_contains (&(auth)->agids, gid)) +-#define isroot(auth) isuid (0, auth) ++ /* CWE-407 fix (hurd-0001): build union sets of permitted UIDs and GIDs from ++ all auth handles once, then verify all requested IDs against the union. ++ Old complexity: O((neuids+nauids+negids+nagids) * nauths * k) where k is ++ idvec size — each requested ID scanned every auth handle linearly. ++ New complexity: O(nauths * k) build + O(N * log k) verify. */ ++ idvec_init (&all_uids); ++ idvec_init (&all_gids); + +- for (i = 0; i < nauths; i++) +- if (auths[i] && isroot (auths[i])) ++ for (j = 0; j < nauths; j++) ++ if (auths[j]) + { +- hasroot = 1; +- break; ++ if (idvec_contains (&auths[j]->euids, 0) ++ || idvec_contains (&auths[j]->auids, 0)) ++ hasroot = 1; ++ err = idvec_merge (&all_uids, &auths[j]->euids) ++ ?: idvec_merge (&all_uids, &auths[j]->auids) ++ ?: idvec_merge (&all_gids, &auths[j]->egids) ++ ?: idvec_merge (&all_gids, &auths[j]->agids); ++ if (err) ++ goto free_idvecs; + } + + if (!hasroot) + { +- int has_it; +- + for (i = 0; i < neuids; i++) +- { +- has_it = 0; +- for (j = 0; j < nauths; j++) +- if (auths[j] && isuid (euids[i], auths[j])) +- { +- has_it = 1; +- break; +- } +- if (!has_it) +- goto eperm; +- } ++ if (!idvec_contains (&all_uids, euids[i])) ++ goto eperm_free; + + for (i = 0; i < nauids; i++) +- { +- has_it = 0; +- for (j = 0; j < nauths; j++) +- if (auths[j] && isuid (auids[i], auths[j])) +- { +- has_it = 1; +- break; +- } +- if (!has_it) +- goto eperm; +- } ++ if (!idvec_contains (&all_uids, auids[i])) ++ goto eperm_free; + + for (i = 0; i < negids; i++) +- { +- has_it = 0; +- for (j = 0; j < nauths; j++) +- if (auths[j] && groupmember (egids[i], auths[j])) +- { +- has_it = 1; +- break; +- } +- if (!has_it) +- goto eperm; +- } ++ if (!idvec_contains (&all_gids, egids[i])) ++ goto eperm_free; + + for (i = 0; i < nagids; i++) +- { +- has_it = 0; +- for (j = 0; j < nauths; j++) +- if (auths[j] && groupmember (agids[i], auths[j])) +- { +- has_it = 1; +- break; +- } +- if (!has_it) +- goto eperm; +- } ++ if (!idvec_contains (&all_gids, agids[i])) ++ goto eperm_free; + } + ++ idvec_free_contents (&all_uids); ++ idvec_free_contents (&all_gids); ++ + err = create_authhandle (&newauth); + + /* Create a new handle with the specified ids. */ +@@ -237,11 +210,22 @@ S_auth_makeauth (struct authhandle *auth, + ports_port_deref (auths[j]); + return err; + ++ eperm_free: ++ idvec_free_contents (&all_uids); ++ idvec_free_contents (&all_gids); + eperm: + for (j = 1; j < nauths; j++) + if (auths[j]) + ports_port_deref (auths[j]); + return EPERM; ++ ++ free_idvecs: ++ idvec_free_contents (&all_uids); ++ idvec_free_contents (&all_gids); ++ for (j = 1; j < nauths; j++) ++ if (auths[j]) ++ ports_port_deref (auths[j]); ++ return err; + } + + /* Transaction handling. */ +diff --git a/libshouldbeinlibc/idvec.c b/libshouldbeinlibc/idvec.c +index c60fc9fb..b78f6a2e 100644 +--- a/libshouldbeinlibc/idvec.c ++++ b/libshouldbeinlibc/idvec.c +@@ -19,6 +19,7 @@ + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + + #include ++#include + #include + + #include "idvec.h" +@@ -176,22 +177,56 @@ idvec_set (struct idvec *idvec, const struct idvec *new) + return idvec_set_ids (idvec, new->ids, new->num); + } + ++/* Compare two uid_t values for qsort/bsearch. */ ++static int ++uid_cmp (const void *a, const void *b) ++{ ++ uid_t ua = *(const uid_t *)a; ++ uid_t ub = *(const uid_t *)b; ++ return (ua > ub) - (ua < ub); ++} ++ + /* Adds each id in the vector IDS (NUM elements long) to IDVEC, as long as it +- wasn't previously in IDVEC. */ ++ wasn't previously in IDVEC. ++ CWE-407 fix (hurd-0006): was O(num * num_old) — linear scan per incoming id. ++ Fixed to O(num_old * log(num_old) + num * log(num_old)) by sorting a copy of ++ the existing ids and using binary search for duplicate detection. */ + error_t + idvec_merge_ids (struct idvec *idvec, const uid_t *ids, unsigned num) + { + error_t err = 0; + unsigned num_old = idvec->num; ++ ++ if (num_old == 0) ++ { ++ /* Nothing to deduplicate against — just append all ids. */ ++ while (num-- > 0 && !err) ++ err = idvec_add (idvec, *ids++); ++ return err; ++ } ++ ++ /* Sort a copy of the existing ids for O(log k) binary search. ++ We copy rather than sort in-place to preserve the original order, ++ which callers may rely on (e.g. euids[0] is the primary uid). */ ++ uid_t sorted[num_old]; ++ memcpy (sorted, idvec->ids, num_old * sizeof (uid_t)); ++ qsort (sorted, num_old, sizeof (uid_t), uid_cmp); ++ + while (num-- > 0 && !err) + { +- unsigned int i; +- for (i = 0; i < num_old; i++) +- if (idvec->ids[i] == *ids) +- break; +- if (i == num_old) +- err = idvec_add (idvec, *ids); +- ids++; ++ uid_t id = *ids++; ++ /* Binary search for id in sorted[0..num_old). */ ++ unsigned lo = 0, hi = num_old; ++ while (lo < hi) ++ { ++ unsigned mid = lo + (hi - lo) / 2; ++ if (sorted[mid] < id) ++ lo = mid + 1; ++ else ++ hi = mid; ++ } ++ if (lo >= num_old || sorted[lo] != id) ++ err = idvec_add (idvec, id); + } + return err; + } +-- +2.43.0 + diff --git a/defects/hurd/patch/0002-CWE-407-hurd-0002-hurd-0003-replace-_ports_notificat.patch b/defects/hurd/patch/0002-CWE-407-hurd-0002-hurd-0003-replace-_ports_notificat.patch new file mode 100644 index 000000000..64e071c69 --- /dev/null +++ b/defects/hurd/patch/0002-CWE-407-hurd-0002-hurd-0003-replace-_ports_notificat.patch @@ -0,0 +1,303 @@ +From de3b12674e0b0b259d449e70fea83f0491f6d937 Mon Sep 17 00:00:00 2001 +From: "russell@unturf.com" +Date: Sat, 4 Apr 2026 20:29:19 -0400 +Subject: [PATCH 2/2] CWE-407 (hurd-0002, hurd-0003): replace + _ports_notifications linked list with hash table + +ports_interrupt_rpc_on_notification and ports_interrupt_notified_rpcs both +scanned the global _ports_notifications linked list in O(N) on every RPC +notification setup and every port-death event respectively. + +Replace the list with _ports_notify_htable (hurd_ihash keyed on mach_port_t) +for O(1) port lookup. Multiple entries with the same port but different 'what' +values (rare; typically MACH_NOTIFY_DEAD_NAME only) are chained via +ports_notify.next/prevp from the primary htable entry. The locp_offset +mechanism auto-maintains htable_locp in each primary entry for O(1) removal. +--- + libports/interrupt-notified-rpcs.c | 83 ++++++++++++++++------- + libports/interrupt-on-notify.c | 103 ++++++++++++++++++----------- + libports/ports.h | 18 +++-- + 3 files changed, 138 insertions(+), 66 deletions(-) + +diff --git a/libports/interrupt-notified-rpcs.c b/libports/interrupt-notified-rpcs.c +index 49a15d0c..a301004b 100644 +--- a/libports/interrupt-notified-rpcs.c ++++ b/libports/interrupt-notified-rpcs.c +@@ -18,10 +18,18 @@ + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + ++#include + #include "ports.h" + +-/* A linked list of ports for which notification has been requested. */ +-struct ports_notify *_ports_notifications; ++/* CWE-407 fix (hurd-0002/0003): hash table of active notification entries, ++ keyed on mach_port_t. Replaces the former _ports_notifications linked list ++ whose O(N) linear scan fired on every RPC setup and every port-death event. ++ Multiple entries with the same port but different 'what' values are chained ++ via ports_notify.next/prevp from the primary htable entry. ++ locp_offset auto-maintains htable_locp in each primary ports_notify entry ++ for O(1) removal via hurd_ihash_locp_remove. */ ++struct hurd_ihash _ports_notify_htable = ++ HURD_IHASH_INITIALIZER (offsetof (struct ports_notify, htable_locp)); + + /* Free lists for notify structures. */ + struct ports_notify *_ports_free_ports_notifies; +@@ -32,25 +40,31 @@ void + ports_interrupt_notified_rpcs (void *object, + mach_port_t port, mach_msg_id_t what) + { +- if (_ports_notifications) +- { +- struct ports_notify *np; ++ struct ports_notify *np; ++ ++ pthread_mutex_lock (&_ports_lock); ++ ++ /* CWE-407 fix: O(1) hash lookup by port instead of O(N) list scan. */ ++ np = hurd_ihash_find (&_ports_notify_htable, (hurd_ihash_key_t) port); + +- pthread_mutex_lock (&_ports_lock); +- for (np = _ports_notifications; np; np = np->next) +- if (np->port == port && np->what == what) ++ /* Walk the short per-port chain to find the matching 'what' value. ++ In practice each port has at most one notification type so this ++ terminates in O(1). */ ++ while (np && np->what != what) ++ np = np->next; ++ ++ if (np) ++ { ++ struct rpc_notify *req; ++ for (req = np->reqs; req; req = req->next_req) ++ if (req->pending) + { +- struct rpc_notify *req; +- for (req = np->reqs; req; req = req->next_req) +- if (req->pending) +- { +- req->pending--; +- hurd_thread_cancel (req->rpc->thread); +- } +- break; ++ req->pending--; ++ hurd_thread_cancel (req->rpc->thread); + } +- pthread_mutex_unlock (&_ports_lock); + } ++ ++ pthread_mutex_unlock (&_ports_lock); + } + + static void +@@ -64,19 +78,38 @@ remove_req (struct rpc_notify *req) + *req->prev_req_p = req->next_req; + + if (np->reqs == 0) +- /* Now NP has no more reqests, so we can free it too. */ ++ /* Now NP has no more requests, so we can free it too. */ + { +- /* Take NP out of the active list... */ +- if (np->next) +- np->next->prevp = np->prevp; +- *np->prevp = np->next; ++ if (np->htable_locp) ++ { ++ /* NP is the primary htable entry for this port. Remove it first, ++ then promote the next per-port entry (if any) as the new primary. */ ++ hurd_ihash_locp_remove (&_ports_notify_htable, np->htable_locp); ++ np->htable_locp = NULL; ++ if (np->next) ++ { ++ struct ports_notify *successor = np->next; ++ successor->prevp = NULL; ++ /* Re-add successor as the htable entry; ihash auto-sets ++ successor->htable_locp via the locp_offset. */ ++ hurd_ihash_add (&_ports_notify_htable, ++ (hurd_ihash_key_t) successor->port, successor); ++ } ++ } ++ else ++ { ++ /* NP is in the per-port chain, not the htable primary entry. */ ++ if (np->next) ++ np->next->prevp = np->prevp; ++ *np->prevp = np->next; ++ } + +- /* And put it on the free list. */ ++ /* Put NP on the free list. */ + np->next = _ports_free_ports_notifies; + _ports_free_ports_notifies = np; + + if (np->pending) +- /* And cancel the associated notification. */ ++ /* Cancel the associated notification. */ + { + mach_port_t old; + error_t err = +@@ -86,7 +119,7 @@ remove_req (struct rpc_notify *req) + &old); + if (! err && old != MACH_PORT_NULL) + mach_port_deallocate (mach_task_self (), old); +- } ++ } + } + } + +diff --git a/libports/interrupt-on-notify.c b/libports/interrupt-on-notify.c +index b358e840..69b904ef 100644 +--- a/libports/interrupt-on-notify.c ++++ b/libports/interrupt-on-notify.c +@@ -60,43 +60,72 @@ ports_interrupt_rpc_on_notification (void *object, + pthread_mutex_lock (&_ports_lock); + } + +- /* Find any existing entry for PORT/WHAT. */ +- for (pn = _ports_notifications; pn; pn = pn->next) +- if (pn->port == port && pn->what == what) +- break; +- +- if (! pn) +- /* A notification on a new port. */ +- { +- pn = _ports_free_ports_notifies; +- +- if (pn) +- _ports_free_ports_notifies = pn->next; +- else +- { +- pn = malloc (sizeof (struct ports_notify)); +- if (! pn) +- /* sigh. Free what we've alloced and return. */ +- { +- new_req->next = _ports_free_rpc_notifies; +- _ports_free_rpc_notifies = new_req; +- pthread_mutex_unlock (&_ports_lock); +- return ENOMEM; +- } +- } +- +- pn->reqs = 0; +- pn->port = port; +- pn->what = what; +- pn->pending = 0; +- pthread_mutex_init (&pn->lock, NULL); +- +- pn->next = _ports_notifications; +- pn->prevp = &_ports_notifications; +- if (_ports_notifications) +- _ports_notifications->prevp = &pn->next; +- _ports_notifications = pn; +- } ++ /* CWE-407 fix (hurd-0002): O(1) hash lookup by port instead of O(N) scan. ++ Walk the short per-port chain to find the matching 'what' value. */ ++ pn = hurd_ihash_find (&_ports_notify_htable, (hurd_ihash_key_t) port); ++ { ++ struct ports_notify *head = pn; ++ while (pn && pn->what != what) ++ pn = pn->next; ++ ++ if (! pn) ++ /* No existing entry for (PORT, WHAT) — allocate a new one. */ ++ { ++ struct ports_notify *new_pn = _ports_free_ports_notifies; ++ ++ if (new_pn) ++ _ports_free_ports_notifies = new_pn->next; ++ else ++ { ++ new_pn = malloc (sizeof (struct ports_notify)); ++ if (! new_pn) ++ /* sigh. Free what we've alloced and return. */ ++ { ++ new_req->next = _ports_free_rpc_notifies; ++ _ports_free_rpc_notifies = new_req; ++ pthread_mutex_unlock (&_ports_lock); ++ return ENOMEM; ++ } ++ } ++ ++ new_pn->reqs = 0; ++ new_pn->port = port; ++ new_pn->what = what; ++ new_pn->pending = 0; ++ new_pn->htable_locp = NULL; ++ pthread_mutex_init (&new_pn->lock, NULL); ++ ++ if (! head) ++ { ++ /* First entry for this port — add to the hash table. */ ++ /* hurd_ihash_add auto-sets new_pn->htable_locp via locp_offset. */ ++ error_t herr = hurd_ihash_add (&_ports_notify_htable, ++ (hurd_ihash_key_t) port, new_pn); ++ if (herr) ++ { ++ new_pn->next = _ports_free_ports_notifies; ++ _ports_free_ports_notifies = new_pn; ++ new_req->next = _ports_free_rpc_notifies; ++ _ports_free_rpc_notifies = new_req; ++ pthread_mutex_unlock (&_ports_lock); ++ return herr; ++ } ++ new_pn->next = NULL; ++ new_pn->prevp = NULL; ++ } ++ else ++ { ++ /* Prepend to the per-port chain after the htable primary entry. */ ++ new_pn->next = head->next; ++ new_pn->prevp = &head->next; ++ if (head->next) ++ head->next->prevp = &new_pn->next; ++ head->next = new_pn; ++ } ++ ++ pn = new_pn; ++ } ++ } + + for (req = rpc->notifies; req; req = req->next) + if (req->notify == pn) +diff --git a/libports/ports.h b/libports/ports.h +index 9299bc40..1b6705e3 100644 +--- a/libports/ports.h ++++ b/libports/ports.h +@@ -121,7 +121,10 @@ struct rpc_notify + struct rpc_notify **prev_req_p; /* who points to this rpc_notify. */ + }; + +-/* A notification request on a (not necessarily registered) port. */ ++/* A notification request on a (not necessarily registered) port. ++ CWE-407 fix (hurd-0002/0003): entries are now indexed by _ports_notify_htable ++ keyed on mach_port_t for O(1) lookup. next/prevp chain entries with the ++ same port but different 'what' values (rare; typically only one per port). */ + struct ports_notify + { + mach_port_t port; /* */ +@@ -130,11 +133,18 @@ struct ports_notify + pthread_mutex_t lock; + + struct rpc_notify *reqs; /* Which rpcs are notified by this port. */ +- struct ports_notify *next, **prevp; /* Linked list of all notified ports. */ ++ /* Per-port chain: entries with the same port but different 'what'. */ ++ struct ports_notify *next, **prevp; ++ /* Hash table location for O(1) removal (only set on the first entry ++ for a given port — the one stored directly in the htable). */ ++ hurd_ihash_locp_t htable_locp; + }; + +-/* A linked list of ports that have had notification requested. */ +-extern struct ports_notify *_ports_notifications; ++/* Hash table of active notification entries, keyed on mach_port_t. ++ CWE-407 fix (hurd-0002/0003): replaces O(N) linked-list scan with O(1) ++ port lookup. Multiple entries with the same port (different 'what') are ++ chained via ports_notify.next/prevp from the htable's stored entry. */ ++extern struct hurd_ihash _ports_notify_htable; + + /* Free lists for notify structures. */ + extern struct ports_notify *_ports_free_ports_notifies; +-- +2.43.0 +