From cb60f9d1da16813879bc18c6906c0a478bb2eec5 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 23 Dec 2024 15:57:01 -0500 Subject: [PATCH] fix(RefCountedSet): Gracefully handle pathological cases Poor hash uniformity and/or a crafted or unlucky input could cause the bounds of the PSL stats array to be exceeded, which caused memory corruption (not good!) -- we avoid such cases now by returning an OutOfMemory error if we're about to insert and there's an item with a PSL in the last slot. --- src/terminal/ref_counted_set.zig | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/terminal/ref_counted_set.zig b/src/terminal/ref_counted_set.zig index eecfae4b59..ddca324820 100644 --- a/src/terminal/ref_counted_set.zig +++ b/src/terminal/ref_counted_set.zig @@ -103,6 +103,12 @@ pub fn RefCountedSet( /// unlikely. Roughly a (1/table_cap)^32 -- with any normal /// table capacity that is so unlikely that it's not worth /// handling. + /// + /// However, that assumes a uniform hash function, which + /// is not guaranteed and can be subverted with a crafted + /// input. We handle this gracefully by returning an error + /// anywhere where we're about to insert if there's any + /// item with a PSL in the last slot of the stats array. psl_stats: [32]Id = [_]Id{0} ** 32, /// The backing store of items @@ -237,6 +243,16 @@ pub fn RefCountedSet( return id; } + // While it should be statistically impossible to exceed the + // bounds of `psl_stats`, the hash function is not perfect and + // in such a case we want to remain stable. If we're about to + // insert an item and there's something with a PSL of `len - 1`, + // we may end up with a PSL of `len` which would exceed the bounds. + // In such a case, we claim to be out of memory. + if (self.psl_stats[self.psl_stats.len - 1] > 0) { + return AddError.OutOfMemory; + } + // If the item doesn't exist, we need an available ID. if (self.next_id >= self.layout.cap) { // Arbitrarily chosen, threshold for rehashing. @@ -284,6 +300,11 @@ pub fn RefCountedSet( if (id < self.next_id) { if (items[id].meta.ref == 0) { + // See comment in `addContext` for details. + if (self.psl_stats[self.psl_stats.len - 1] > 0) { + return AddError.OutOfMemory; + } + self.deleteItem(base, id, ctx); const added_id = self.upsert(base, value, id, ctx);