diff options
author | Matt Mastracci <matthew@mastracci.com> | 2023-08-28 15:28:39 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-28 15:28:39 -0600 |
commit | 7adaf613bfbeab0f6aed92294e3eea912990d819 (patch) | |
tree | 0484633b318a0543f8c7d78ac07f31b0848c936f /ext/node | |
parent | 9198bbd454c39f4d62f43ea729affe8cb789304a (diff) |
fix(ext/node): shared global buffer unlock correctness fix (#20314)
The fix for #20188 was not entirely correct -- we were unlocking the
global buffer incorrectly. This PR introduces a lock state that ensures
we only unlock a lock we have taken out.
Diffstat (limited to 'ext/node')
-rw-r--r-- | ext/node/polyfills/internal_binding/stream_wrap.ts | 41 |
1 files changed, 34 insertions, 7 deletions
diff --git a/ext/node/polyfills/internal_binding/stream_wrap.ts b/ext/node/polyfills/internal_binding/stream_wrap.ts index 8e976da2c..f59acb9b9 100644 --- a/ext/node/polyfills/internal_binding/stream_wrap.ts +++ b/ext/node/polyfills/internal_binding/stream_wrap.ts @@ -311,21 +311,37 @@ export class LibuvStreamWrap extends HandleWrap { /** Internal method for reading from the attached stream. */ async #read() { - const isOwnedBuf = bufLocked; - let buf = bufLocked ? new Uint8Array(SUGGESTED_SIZE) : BUF; - bufLocked = true; + // Lock safety: We must hold this lock until we are certain that buf is no longer used + // This setup code is a little verbose, but we need to be careful about buffer management + let buf, locked = false; + if (bufLocked) { + // Already locked, allocate + buf = new Uint8Array(SUGGESTED_SIZE); + } else { + // Not locked, take the buffer + lock + buf = BUF; + locked = bufLocked = true; + } try { let nread: number | null; const ridBefore = this[kStreamBaseField]!.rid; try { nread = await this[kStreamBaseField]!.read(buf); } catch (e) { + // Lock safety: we know that the buffer will not be used in this function again + // All exits from this block either return or re-assign buf to a different value + if (locked) { + bufLocked = locked = false; + } + // Try to read again if the underlying stream resource // changed. This can happen during TLS upgrades (eg. STARTTLS) if (ridBefore != this[kStreamBaseField]!.rid) { return this.#read(); } + buf = new Uint8Array(0); + if ( e instanceof Deno.errors.Interrupted || e instanceof Deno.errors.BadResource @@ -339,8 +355,6 @@ export class LibuvStreamWrap extends HandleWrap { } else { nread = codeMap.get("UNKNOWN")!; } - - buf = new Uint8Array(0); } nread ??= codeMap.get("EOF")!; @@ -351,7 +365,17 @@ export class LibuvStreamWrap extends HandleWrap { this.bytesRead += nread; } - buf = isOwnedBuf ? buf.subarray(0, nread) : buf.slice(0, nread); + // We release the lock early so a re-entrant read can make use of the shared buffer, but + // we need to make a copy of the data in the shared buffer. + if (locked) { + // Lock safety: we know that the buffer will not be used in this function again + // We're making a copy of data that lives in the shared buffer + buf = buf.slice(0, nread); + bufLocked = locked = false; + } else { + // The buffer isn't owned, so let's create a subarray view + buf = buf.subarray(0, nread); + } streamBaseState[kArrayBufferOffset] = 0; @@ -365,7 +389,10 @@ export class LibuvStreamWrap extends HandleWrap { this.#read(); } } finally { - bufLocked = false; + // Lock safety: we know that the buffer will not be used in this function again + if (locked) { + bufLocked = locked = false; + } } } |