summaryrefslogtreecommitdiff
path: root/ext/node
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2023-08-28 15:28:39 -0600
committerGitHub <noreply@github.com>2023-08-28 15:28:39 -0600
commit7adaf613bfbeab0f6aed92294e3eea912990d819 (patch)
tree0484633b318a0543f8c7d78ac07f31b0848c936f /ext/node
parent9198bbd454c39f4d62f43ea729affe8cb789304a (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.ts41
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;
+ }
}
}