Skip to content

Commit a9cfd11

Browse files
authored
fix(node): Guard against invalid maxSpanWaitDuration values (#14632)
Today, if you pass e.g. `Infinity` or another large number to `maxSpanWaitDuration`, the SDK will error out because we try to do `new Array(maxSpanWaitDuration)`, which is impossible. Ideally, we can properly allow an infinite wait time, but for now this adds checks to ensure only valid values are passed in there, and we do not explode. See #14154 (comment)
1 parent f962eef commit a9cfd11

File tree

2 files changed

+103
-1
lines changed

2 files changed

+103
-1
lines changed

packages/node/src/sdk/initOtel.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { GLOBAL_OBJ, SDK_VERSION, consoleSandbox, logger } from '@sentry/core';
1111
import { SentryPropagator, SentrySampler, SentrySpanProcessor } from '@sentry/opentelemetry';
1212
import { createAddHookMessageChannel } from 'import-in-the-middle';
13+
import { DEBUG_BUILD } from '../debug-build';
1314
import { getOpenTelemetryInstrumentationToPreload } from '../integrations/tracing';
1415
import { SentryContextManager } from '../otel/contextManager';
1516
import type { EsmLoaderHookOptions } from '../types';
@@ -18,6 +19,9 @@ import type { NodeClient } from './client';
1819

1920
declare const __IMPORT_META_URL_REPLACEMENT__: string;
2021

22+
// About 277h - this must fit into new Array(len)!
23+
const MAX_MAX_SPAN_WAIT_DURATION = 1_000_000;
24+
2125
/**
2226
* Initialize OpenTelemetry for Node.
2327
*/
@@ -138,7 +142,7 @@ export function setupOtel(client: NodeClient): BasicTracerProvider {
138142
forceFlushTimeoutMillis: 500,
139143
spanProcessors: [
140144
new SentrySpanProcessor({
141-
timeout: client.getOptions().maxSpanWaitDuration,
145+
timeout: _clampSpanProcessorTimeout(client.getOptions().maxSpanWaitDuration),
142146
}),
143147
],
144148
});
@@ -152,6 +156,26 @@ export function setupOtel(client: NodeClient): BasicTracerProvider {
152156
return provider;
153157
}
154158

159+
/** Just exported for tests. */
160+
export function _clampSpanProcessorTimeout(maxSpanWaitDuration: number | undefined): number | undefined {
161+
if (maxSpanWaitDuration == null) {
162+
return undefined;
163+
}
164+
165+
// We guard for a max. value here, because we create an array with this length
166+
// So if this value is too large, this would fail
167+
if (maxSpanWaitDuration > MAX_MAX_SPAN_WAIT_DURATION) {
168+
DEBUG_BUILD &&
169+
logger.warn(`\`maxSpanWaitDuration\` is too high, using the maximum value of ${MAX_MAX_SPAN_WAIT_DURATION}`);
170+
return MAX_MAX_SPAN_WAIT_DURATION;
171+
} else if (maxSpanWaitDuration <= 0 || Number.isNaN(maxSpanWaitDuration)) {
172+
DEBUG_BUILD && logger.warn('`maxSpanWaitDuration` must be a positive number, using default value instead.');
173+
return undefined;
174+
}
175+
176+
return maxSpanWaitDuration;
177+
}
178+
155179
/**
156180
* Setup the OTEL logger to use our own logger.
157181
*/
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { logger } from '@sentry/core';
2+
import { _clampSpanProcessorTimeout } from '../../src/sdk/initOtel';
3+
4+
describe('_clampSpanProcessorTimeout', () => {
5+
beforeEach(() => {
6+
jest.clearAllMocks();
7+
});
8+
9+
it('works with undefined', () => {
10+
const loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
11+
const timeout = _clampSpanProcessorTimeout(undefined);
12+
expect(timeout).toBe(undefined);
13+
expect(loggerWarnSpy).not.toHaveBeenCalled();
14+
});
15+
16+
it('works with positive number', () => {
17+
const loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
18+
const timeout = _clampSpanProcessorTimeout(10);
19+
expect(timeout).toBe(10);
20+
expect(loggerWarnSpy).not.toHaveBeenCalled();
21+
});
22+
23+
it('works with 0', () => {
24+
const loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
25+
const timeout = _clampSpanProcessorTimeout(0);
26+
expect(timeout).toBe(undefined);
27+
expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
28+
expect(loggerWarnSpy).toHaveBeenCalledWith(
29+
'`maxSpanWaitDuration` must be a positive number, using default value instead.',
30+
);
31+
});
32+
33+
it('works with negative number', () => {
34+
const loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
35+
const timeout = _clampSpanProcessorTimeout(-10);
36+
expect(timeout).toBe(undefined);
37+
expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
38+
expect(loggerWarnSpy).toHaveBeenCalledWith(
39+
'`maxSpanWaitDuration` must be a positive number, using default value instead.',
40+
);
41+
});
42+
43+
it('works with -Infinity', () => {
44+
const loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
45+
const timeout = _clampSpanProcessorTimeout(-Infinity);
46+
expect(timeout).toBe(undefined);
47+
expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
48+
expect(loggerWarnSpy).toHaveBeenCalledWith(
49+
'`maxSpanWaitDuration` must be a positive number, using default value instead.',
50+
);
51+
});
52+
53+
it('works with Infinity', () => {
54+
const loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
55+
const timeout = _clampSpanProcessorTimeout(Infinity);
56+
expect(timeout).toBe(1_000_000);
57+
expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
58+
expect(loggerWarnSpy).toHaveBeenCalledWith('`maxSpanWaitDuration` is too high, using the maximum value of 1000000');
59+
});
60+
61+
it('works with large number', () => {
62+
const loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
63+
const timeout = _clampSpanProcessorTimeout(1_000_000_000);
64+
expect(timeout).toBe(1_000_000);
65+
expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
66+
expect(loggerWarnSpy).toHaveBeenCalledWith('`maxSpanWaitDuration` is too high, using the maximum value of 1000000');
67+
});
68+
69+
it('works with NaN', () => {
70+
const loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
71+
const timeout = _clampSpanProcessorTimeout(NaN);
72+
expect(timeout).toBe(undefined);
73+
expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
74+
expect(loggerWarnSpy).toHaveBeenCalledWith(
75+
'`maxSpanWaitDuration` must be a positive number, using default value instead.',
76+
);
77+
});
78+
});

0 commit comments

Comments
 (0)