Skip to content

Commit f2e6e74

Browse files
authored
Fix interpolation search for extreme manifests (#8105)
1 parent 41555f8 commit f2e6e74

File tree

3 files changed

+117
-6
lines changed

3 files changed

+117
-6
lines changed

.changeset/orange-rockets-leave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/workers-shared": patch
3+
---
4+
5+
fix: Handles a divide by zero error that could occur when searching large manifests

packages/workers-shared/asset-worker/src/assets-manifest.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,22 @@ export const interpolationSearch = (
116116
arr.byteOffset + high * ENTRY_SIZE,
117117
PATH_HASH_SIZE
118118
);
119+
const lowValueNumber = uint8ArrayToNumber(lowValue);
120+
const highValueNumber = uint8ArrayToNumber(highValue);
121+
const denominator = highValueNumber - lowValueNumber;
122+
if (denominator < 0n) {
123+
return false;
124+
}
125+
const numerator = searchValueNumber - lowValueNumber;
126+
if (numerator < 0n) {
127+
return false;
128+
}
119129
const mid = Math.floor(
120-
Number(
121-
BigInt(low) +
122-
(BigInt(high - low) *
123-
(searchValueNumber - uint8ArrayToNumber(lowValue))) /
124-
(uint8ArrayToNumber(highValue) - uint8ArrayToNumber(lowValue))
125-
)
130+
Number(BigInt(low) + (BigInt(high - low) * numerator) / denominator)
126131
);
132+
if (mid < low || mid > high) {
133+
return false;
134+
}
127135
const current = new Uint8Array(
128136
arr.buffer,
129137
arr.byteOffset + mid * ENTRY_SIZE,

packages/workers-shared/asset-worker/tests/assets-manifest.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,21 @@ describe("search methods", async () => {
221221
).toEqual(hexToBytes(searchEntry.contentHash));
222222
}
223223
});
224+
225+
it("returns false for non-existent paths", async () => {
226+
const { manifest } = await makeManifestOfLength(10);
227+
for (const searchEntry of [
228+
new Uint8Array(PATH_HASH_SIZE),
229+
new Uint8Array(PATH_HASH_SIZE).fill(127),
230+
new Uint8Array(PATH_HASH_SIZE).fill(255),
231+
]) {
232+
const foundEntry = binarySearch(
233+
new Uint8Array(manifest, HEADER_SIZE),
234+
searchEntry
235+
);
236+
expect(foundEntry).toBe(false);
237+
}
238+
});
224239
});
225240

226241
describe("interpolation search", () => {
@@ -308,5 +323,88 @@ describe("search methods", async () => {
308323
).toEqual(hexToBytes(searchEntry.contentHash));
309324
}
310325
});
326+
327+
it("returns false for non-existent paths", async () => {
328+
const { manifest } = await makeManifestOfLength(10);
329+
for (const searchEntry of [
330+
new Uint8Array(PATH_HASH_SIZE),
331+
new Uint8Array(PATH_HASH_SIZE).fill(127),
332+
new Uint8Array(PATH_HASH_SIZE).fill(255),
333+
]) {
334+
const foundEntry = interpolationSearch(
335+
new Uint8Array(manifest, HEADER_SIZE),
336+
searchEntry
337+
);
338+
expect(foundEntry).toBe(false);
339+
}
340+
});
341+
342+
it("doesn't throw 'RangeError: Division by zero' with extreme manifests", async () => {
343+
const smallEntry = {
344+
path: "/small",
345+
pathHashBytes: new Uint8Array(PATH_HASH_SIZE),
346+
contentHash: "00000000000000000000000000000000",
347+
};
348+
const largeEntry = {
349+
path: "/large",
350+
pathHashBytes: new Uint8Array(PATH_HASH_SIZE).fill(255),
351+
contentHash: "ffffffffffffffffffffffffffffffff",
352+
};
353+
const entries = [smallEntry, largeEntry];
354+
entries.sort((a, b) => compare(a.pathHashBytes, b.pathHashBytes));
355+
356+
const assetManifestBytes = new Uint8Array(
357+
HEADER_SIZE + entries.length * ENTRY_SIZE
358+
);
359+
360+
for (const [i, { pathHashBytes, contentHash }] of entries.entries()) {
361+
const contentHashBytes = hexToBytes(contentHash);
362+
const entryOffset = HEADER_SIZE + i * ENTRY_SIZE;
363+
364+
assetManifestBytes.set(pathHashBytes, entryOffset + PATH_HASH_OFFSET);
365+
assetManifestBytes.set(
366+
contentHashBytes,
367+
entryOffset + CONTENT_HASH_OFFSET
368+
);
369+
}
370+
371+
const foundSmallEntry = interpolationSearch(
372+
new Uint8Array(assetManifestBytes.buffer, HEADER_SIZE),
373+
smallEntry.pathHashBytes
374+
) as Uint8Array;
375+
expect(foundSmallEntry).not.toBe(false);
376+
expect(
377+
new Uint8Array(
378+
foundSmallEntry.buffer,
379+
CONTENT_HASH_OFFSET + foundSmallEntry.byteOffset,
380+
CONTENT_HASH_SIZE
381+
)
382+
).toEqual(hexToBytes(smallEntry.contentHash));
383+
384+
const foundLargeEntry = interpolationSearch(
385+
new Uint8Array(assetManifestBytes.buffer, HEADER_SIZE),
386+
largeEntry.pathHashBytes
387+
) as Uint8Array;
388+
expect(foundLargeEntry).not.toBe(false);
389+
expect(
390+
new Uint8Array(
391+
foundLargeEntry.buffer,
392+
CONTENT_HASH_OFFSET + foundLargeEntry.byteOffset,
393+
CONTENT_HASH_SIZE
394+
)
395+
).toEqual(hexToBytes(largeEntry.contentHash));
396+
397+
for (const searchEntry of [
398+
new Uint8Array(PATH_HASH_SIZE).fill(1),
399+
new Uint8Array(PATH_HASH_SIZE).fill(127),
400+
new Uint8Array(PATH_HASH_SIZE).fill(254),
401+
]) {
402+
const foundEntry = interpolationSearch(
403+
new Uint8Array(assetManifestBytes.buffer, HEADER_SIZE),
404+
searchEntry
405+
);
406+
expect(foundEntry).toBe(false);
407+
}
408+
});
311409
});
312410
});

0 commit comments

Comments
 (0)