hurd: add patches for hurd-0001/0006 (auth) and hurd-0002/0003 (notify htable)

This commit is contained in:
russell@unturf.com 2026-04-04 20:29:35 -04:00
parent e94a4b17b3
commit 360b7abb54
2 changed files with 551 additions and 0 deletions

View file

@ -0,0 +1,248 @@
From a56d5fc886eaea781d1033bf5dc1cd94e4dad4a0 Mon Sep 17 00:00:00 2001
From: "russell@unturf.com" <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 <malloc.h>
+#include <stdlib.h>
#include <string.h>
#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

View file

@ -0,0 +1,303 @@
From de3b12674e0b0b259d449e70fea83f0491f6d937 Mon Sep 17 00:00:00 2001
From: "russell@unturf.com" <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 <stddef.h>
#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