main
js 141 lines 4.25 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import {enableTaint} from 'shared/ReactFeatureFlags';
11
12 import getPrototypeOf from 'shared/getPrototypeOf';
13
14 import binaryToComparableString from 'shared/binaryToComparableString';
15
16 import ReactSharedInternals from './ReactSharedInternalsServer';
17 const {
18 TaintRegistryObjects,
19 TaintRegistryValues,
20 TaintRegistryByteLengths,
21 TaintRegistryPendingRequests,
22 } = ReactSharedInternals;
23
24 interface Reference {}
25
26 // This is the shared constructor of all typed arrays.
27 const TypedArrayConstructor = getPrototypeOf(Uint32Array.prototype).constructor;
28
29 const defaultMessage =
30 'A tainted value was attempted to be serialized to a Client Component or Action closure. ' +
31 'This would leak it to the client.';
32
33 function cleanup(entryValue: string | bigint): void {
34 const entry = TaintRegistryValues.get(entryValue);
35 if (entry !== undefined) {
36 TaintRegistryPendingRequests.forEach(function (requestQueue) {
37 requestQueue.push(entryValue);
38 entry.count++;
39 });
40 if (entry.count === 1) {
41 TaintRegistryValues.delete(entryValue);
42 } else {
43 entry.count--;
44 }
45 }
46 }
47
48 // If FinalizationRegistry doesn't exist, we assume that objects life forever.
49 // E.g. the whole VM is just the lifetime of a request.
50 const finalizationRegistry =
51 typeof FinalizationRegistry === 'function'
52 ? new FinalizationRegistry(cleanup)
53 : null;
54
55 export function taintUniqueValue(
56 message: ?string,
57 lifetime: Reference,
58 value: string | bigint | $ArrayBufferView,
59 ): void {
60 if (!enableTaint) {
61 throw new Error('Not implemented.');
62 }
63 // eslint-disable-next-line react-internal/safe-string-coercion
64 message = '' + (message || defaultMessage);
65 if (
66 // $FlowFixMe[invalid-compare]
67 lifetime === null ||
68 (typeof lifetime !== 'object' && typeof lifetime !== 'function')
69 ) {
70 throw new Error(
71 'To taint a value, a lifetime must be defined by passing an object that holds ' +
72 'the value.',
73 );
74 }
75 let entryValue: string | bigint;
76 if (typeof value === 'string' || typeof value === 'bigint') {
77 // Use as is.
78 entryValue = value;
79 } else if (
80 value instanceof TypedArrayConstructor ||
81 value instanceof DataView
82 ) {
83 // For now, we just convert binary data to a string so that we can just use the native
84 // hashing in the Map implementation. It doesn't really matter what form the string
85 // take as long as it's the same when we look it up.
86 // We're not too worried about collisions since this should be a high entropy value.
87 TaintRegistryByteLengths.add(value.byteLength);
88 entryValue = binaryToComparableString(value);
89 } else {
90 // $FlowFixMe[invalid-compare]
91 const kind = value === null ? 'null' : typeof value;
92 if (kind === 'object' || kind === 'function') {
93 throw new Error(
94 'taintUniqueValue cannot taint objects or functions. Try taintObjectReference instead.',
95 );
96 }
97 throw new Error(
98 'Cannot taint a ' +
99 kind +
100 ' because the value is too general and not unique enough to block globally.',
101 );
102 }
103 const existingEntry = TaintRegistryValues.get(entryValue);
104 if (existingEntry === undefined) {
105 TaintRegistryValues.set(entryValue, {
106 message,
107 count: 1,
108 });
109 } else {
110 existingEntry.count++;
111 }
112 if (finalizationRegistry !== null) {
113 finalizationRegistry.register(lifetime, entryValue);
114 }
115 }
116
117 export function taintObjectReference(
118 message: ?string,
119 object: Reference,
120 ): void {
121 if (!enableTaint) {
122 throw new Error('Not implemented.');
123 }
124 // eslint-disable-next-line react-internal/safe-string-coercion
125 message = '' + (message || defaultMessage);
126 if (typeof object === 'string' || typeof object === 'bigint') {
127 throw new Error(
128 'Only objects or functions can be passed to taintObjectReference. Try taintUniqueValue instead.',
129 );
130 }
131 if (
132 // $FlowFixMe[invalid-compare]
133 object === null ||
134 (typeof object !== 'object' && typeof object !== 'function')
135 ) {
136 throw new Error(
137 'Only objects or functions can be passed to taintObjectReference.',
138 );
139 }
140 TaintRegistryObjects.set(object, message);
141 }