Skip to content

Commit 0bec950

Browse files
committed
Optimize binary value checking
Instead of copying all typed arrays that we serialize into a string, we only do that if the length of the data is the length of any tainted binary value first. You're not really supposed to use this with arbitrary binary data but mostly things like hashes or tokens that end up with the same length.
1 parent e151dd2 commit 0bec950

File tree

4 files changed

+23
-10
lines changed

4 files changed

+23
-10
lines changed

packages/react-server/src/ReactFlightServer.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export type Request = {
206206
const {
207207
TaintRegistryObjects,
208208
TaintRegistryValues,
209+
TaintRegistryByteLengths,
209210
ReactCurrentDispatcher,
210211
ReactCurrentCache,
211212
} = ReactServerSharedInternals;
@@ -796,14 +797,15 @@ function serializeTypedArray(
796797
typedArray: $ArrayBufferView,
797798
): string {
798799
if (enableTaint) {
799-
// TODO: This is way too slow for the common case. We should first check if
800-
// there even could be a match such as if there are any tainted arrays of
801-
// equal size.
802-
const tainted = TaintRegistryValues.get(
803-
binaryToComparableString(typedArray),
804-
);
805-
if (tainted !== undefined) {
806-
throwTaintViolation(tainted.message);
800+
if (TaintRegistryByteLengths.has(typedArray.byteLength)) {
801+
// If we have had any tainted values of this length, we check
802+
// to see if these bytes matches any entries in the registry.
803+
const tainted = TaintRegistryValues.get(
804+
binaryToComparableString(typedArray),
805+
);
806+
if (tainted !== undefined) {
807+
throwTaintViolation(tainted.message);
808+
}
807809
}
808810
}
809811
request.pendingChunks += 2;

packages/react/src/ReactServerSharedInternals.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
99
import ReactCurrentCache from './ReactCurrentCache';
10-
import {TaintRegistryObjects, TaintRegistryValues} from './ReactTaintRegistry';
10+
import {
11+
TaintRegistryObjects,
12+
TaintRegistryValues,
13+
TaintRegistryByteLengths,
14+
} from './ReactTaintRegistry';
1115

1216
import {enableTaint} from 'shared/ReactFeatureFlags';
1317

@@ -19,6 +23,8 @@ const ReactServerSharedInternals = {
1923
if (enableTaint) {
2024
ReactServerSharedInternals.TaintRegistryObjects = TaintRegistryObjects;
2125
ReactServerSharedInternals.TaintRegistryValues = TaintRegistryValues;
26+
ReactServerSharedInternals.TaintRegistryByteLengths =
27+
TaintRegistryByteLengths;
2228
}
2329

2430
export default ReactServerSharedInternals;

packages/react/src/ReactTaint.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {enableTaint, enableBinaryFlight} from 'shared/ReactFeatureFlags';
1212
import binaryToComparableString from 'shared/binaryToComparableString';
1313

1414
import ReactServerSharedInternals from './ReactServerSharedInternals';
15-
const {TaintRegistryObjects, TaintRegistryValues} = ReactServerSharedInternals;
15+
const {TaintRegistryObjects, TaintRegistryValues, TaintRegistryByteLengths} =
16+
ReactServerSharedInternals;
1617

1718
interface Reference {}
1819

@@ -74,6 +75,7 @@ export function taintValue(
7475
// hashing in the Map implementation. It doesn't really matter what form the string
7576
// take as long as it's the same when we look it up.
7677
// We're not too worried about collisions since this should be a high entropy value.
78+
TaintRegistryByteLengths.add(value.byteLength);
7779
entryValue = binaryToComparableString(value);
7880
} else {
7981
const kind = value === null ? 'null' : typeof value;

packages/react/src/ReactTaintRegistry.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ type TaintEntry = {
1616

1717
export const TaintRegistryObjects: WeakMap<Reference, string> = new WeakMap();
1818
export const TaintRegistryValues: Map<string | bigint, TaintEntry> = new Map();
19+
// Byte lengths of all binary values we've ever seen. We don't both refcounting this.
20+
// We expect to see only a few lengths here such as the length of token.
21+
export const TaintRegistryByteLengths: Set<number> = new Set();

0 commit comments

Comments
 (0)