From 0f6a66c72b270b3c78dd30e697dcd75804e731e4 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sun, 2 Aug 2026 00:09:45 -0700 Subject: [PATCH] Validate payload to WASM worker and use UUID to ensure only relevant results are read back in host code. --- src/lib/generate/wasm/index.ts | 17 ++++++----- src/lib/generate/wasm/worker.ts | 51 +++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/lib/generate/wasm/index.ts b/src/lib/generate/wasm/index.ts index a71e82c..a1e788a 100644 --- a/src/lib/generate/wasm/index.ts +++ b/src/lib/generate/wasm/index.ts @@ -57,17 +57,18 @@ async function init (hash: bigint, difficulty: bigint, effort: number): Promise< async function workersStarted () { return new Promise(async (ready, fail): Promise => { + const id = crypto.randomUUID() const resolutions = [] for (const w of workers) { resolutions.push(new Promise((resolve, reject): void => { w.onerror = reject w.onmessage = (msg) => { const { data } = msg - if (data.url === url) { + if (data.url === url && data.id === id) { data.result === 'started' ? resolve(data) : reject(data) } } - w.postMessage({ start: true, url }) + w.postMessage({ url, id, start: true }) })) } Promise.all(resolutions).then(ready).catch(fail) @@ -76,6 +77,7 @@ async function workersStarted () { async function dispatch (): Promise { return new Promise((resolve, reject) => { + const id = crypto.randomUUID() const seed = crypto.getRandomValues(new BigUint64Array(workers.length)) const attempts = [] for (let i = 0; i < workers.length; i++) { @@ -85,13 +87,13 @@ async function dispatch (): Promise { w.onerror = err w.onmessage = (msg) => { const { data } = msg - if (data.url === url) { + if (data.url === url && data.id === id) { logger.log(`received result ${typeof data.result} "${data.result}" from worker ${i}`) found(data.result) } } - logger.log(`sending data to worker ${i}`, { ...data, url }) - w.postMessage({ ...data, url }) + logger.log(`sending data to worker ${i}`, { url, id, ...data }) + w.postMessage({ url, id, ...data }) })) } Promise.all(attempts) @@ -105,6 +107,7 @@ async function dispatch (): Promise { async function workersStopped (): Promise { return new Promise(stopped => { + const id = crypto.randomUUID() try { const attempts = [] for (let i = 0; i < workers.length; i++) { @@ -113,11 +116,11 @@ async function workersStopped (): Promise { w.onerror = reject w.onmessage = (msg) => { const { data } = msg - if (data.url === url) { + if (data.url === url && data.id === id) { data.result === 'stopped' ? resolve(true) : reject(i) } } - w.postMessage({ stop: true, url }) + w.postMessage({ url, id, stop: true }) })) } Promise.allSettled(attempts).then(results => { diff --git a/src/lib/generate/wasm/worker.ts b/src/lib/generate/wasm/worker.ts index 17bd5b3..aaf1e10 100644 --- a/src/lib/generate/wasm/worker.ts +++ b/src/lib/generate/wasm/worker.ts @@ -32,21 +32,41 @@ const worker = async (compute: number[]): Promise => { } } - async function handleMessage (msg: any): Promise { - if (msg?.data?.url !== self.location.href) return - let result: any = null + async function handleMessage (msg: unknown): Promise { + if (msg == null + || typeof msg !== 'object' + || !('data' in msg) + || msg.data == null + || typeof msg.data !== 'object' + || !('url' in msg.data) + || typeof msg.data.url !== 'string' + || !('id' in msg.data) + || typeof msg.data.id !== 'string' + ) return + + const data = msg.data + const { url, id } = data + if (url !== self.location.href) return + + let result: undefined | string + try { - const { data } = msg if (!isReady) await setup() const hashArray = new BigUint64Array(4) const hashView = new DataView(hashArray.buffer) - if (data.start) { + if ('start' in data && data.start) { result = 'started' - } else if (data.stop) { + } else if ('stop' in data && data.stop) { removeEventListener('message', handleMessage) result = 'stopped' - } else { + } else if ('seed' in data + && typeof data.seed === 'string' + && 'difficulty' in data + && typeof data.difficulty === 'string' + && 'hash' in data + && typeof data.hash === 'string' + ) { const seed = BigInt(`0x${data.seed}`) const difficulty = BigInt(`0x${data.difficulty}`) for (let i = 0; i < data.hash.length; i += 16) { @@ -62,20 +82,15 @@ const worker = async (compute: number[]): Promise => { workView.setBigUint64(0, work, true) result = workArray[0].toString(16).padStart(16, '0') } - } catch (err: unknown) { - if (typeof err === 'object' && err != null) { - const e = err as Record - if (typeof e.message === 'string' && e.message.includes('divide by zero')) { - result = '' - } else { - result = e.message - } + } catch (e: unknown) { + if (e != null && typeof e === 'object' && 'message' in e && typeof e.message === 'string') { + result = e.message.includes('divide by zero') ? '' : e.message } else { - result = JSON.stringify(err) + result = JSON.stringify(e) } } finally { - if (result != null) { - postMessage({ result, url: self.location.href }) + if (result !== undefined) { + postMessage({ url, id, result }) } addEventListener('message', handleMessage) } -- 2.52.0