High severity7.1NVD Advisory· Published May 7, 2024· Updated Apr 15, 2026
CVE-2024-34342
CVE-2024-34342
Description
react-pdf displays PDFs in React apps. If PDF.js is used to load a malicious PDF, and PDF.js is configured with isEvalSupported set to true (which is the default value), unrestricted attacker-controlled JavaScript will be executed in the context of the hosting domain. This vulnerability is fixed in 7.7.3 and 8.0.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
react-pdfnpm | < 7.7.3 | 7.7.3 |
react-pdfnpm | >= 8.0.0, < 8.0.2 | 8.0.2 |
Patches
3208f28dd47feForce isEvalSupported to false
1 file changed · +1 −1
packages/react-pdf/src/Document.tsx+1 −1 modified@@ -518,7 +518,7 @@ const Document = forwardRef(function Document( return; } - const optionsWithModifiedIsEvalSupported: Options = { ...options, isEvalSupported: true }; + const optionsWithModifiedIsEvalSupported: Options = { ...options, isEvalSupported: false }; const documentInitParams: Source = { ...source,
671e6eaa2e37Force isEvalSupported to false
1 file changed · +1 −1
packages/react-pdf/src/Document.tsx+1 −1 modified@@ -505,7 +505,7 @@ const Document = forwardRef(function Document( return; } - const optionsWithModifiedIsEvalSupported: Options = { ...options, isEvalSupported: true }; + const optionsWithModifiedIsEvalSupported: Options = { ...options, isEvalSupported: false }; const documentInitParams: Source = { ...source,
85e64b5c16c9Merge pull request #18015 from calixteman/rm_eval_font_loader
5 files changed · +150 −61
src/core/evaluator.js+10 −1 modified@@ -4391,6 +4391,15 @@ class PartialEvaluator { } } + let fontMatrix = dict.getArray("FontMatrix"); + if ( + !Array.isArray(fontMatrix) || + fontMatrix.length !== 6 || + fontMatrix.some(x => typeof x !== "number") + ) { + fontMatrix = FONT_IDENTITY_MATRIX; + } + const properties = { type, name: fontName.name, @@ -4403,7 +4412,7 @@ class PartialEvaluator { loadedName: baseDict.loadedName, composite, fixedPitch: false, - fontMatrix: dict.getArray("FontMatrix") || FONT_IDENTITY_MATRIX, + fontMatrix, firstChar, lastChar, toUnicode,
src/core/font_renderer.js+51 −26 modified@@ -16,6 +16,7 @@ import { bytesToString, FONT_IDENTITY_MATRIX, + FontRenderOps, FormatError, unreachable, warn, @@ -180,13 +181,13 @@ function lookupCmap(ranges, unicode) { function compileGlyf(code, cmds, font) { function moveTo(x, y) { - cmds.push({ cmd: "moveTo", args: [x, y] }); + cmds.add(FontRenderOps.MOVE_TO, [x, y]); } function lineTo(x, y) { - cmds.push({ cmd: "lineTo", args: [x, y] }); + cmds.add(FontRenderOps.LINE_TO, [x, y]); } function quadraticCurveTo(xa, ya, x, y) { - cmds.push({ cmd: "quadraticCurveTo", args: [xa, ya, x, y] }); + cmds.add(FontRenderOps.QUADRATIC_CURVE_TO, [xa, ya, x, y]); } let i = 0; @@ -247,20 +248,22 @@ function compileGlyf(code, cmds, font) { if (subglyph) { // TODO: the transform should be applied only if there is a scale: // https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1205 - cmds.push( - { cmd: "save" }, - { - cmd: "transform", - args: [scaleX, scale01, scale10, scaleY, x, y], - } - ); + cmds.add(FontRenderOps.SAVE); + cmds.add(FontRenderOps.TRANSFORM, [ + scaleX, + scale01, + scale10, + scaleY, + x, + y, + ]); if (!(flags & 0x02)) { // TODO: we must use arg1 and arg2 to make something similar to: // https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1209 } compileGlyf(subglyph, cmds, font); - cmds.push({ cmd: "restore" }); + cmds.add(FontRenderOps.RESTORE); } } while (flags & 0x20); } else { @@ -365,13 +368,13 @@ function compileGlyf(code, cmds, font) { function compileCharString(charStringCode, cmds, font, glyphId) { function moveTo(x, y) { - cmds.push({ cmd: "moveTo", args: [x, y] }); + cmds.add(FontRenderOps.MOVE_TO, [x, y]); } function lineTo(x, y) { - cmds.push({ cmd: "lineTo", args: [x, y] }); + cmds.add(FontRenderOps.LINE_TO, [x, y]); } function bezierCurveTo(x1, y1, x2, y2, x, y) { - cmds.push({ cmd: "bezierCurveTo", args: [x1, y1, x2, y2, x, y] }); + cmds.add(FontRenderOps.BEZIER_CURVE_TO, [x1, y1, x2, y2, x, y]); } const stack = []; @@ -544,7 +547,8 @@ function compileCharString(charStringCode, cmds, font, glyphId) { const bchar = stack.pop(); y = stack.pop(); x = stack.pop(); - cmds.push({ cmd: "save" }, { cmd: "translate", args: [x, y] }); + cmds.add(FontRenderOps.SAVE); + cmds.add(FontRenderOps.TRANSLATE, [x, y]); let cmap = lookupCmap( font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]]) @@ -555,7 +559,7 @@ function compileCharString(charStringCode, cmds, font, glyphId) { font, cmap.glyphId ); - cmds.push({ cmd: "restore" }); + cmds.add(FontRenderOps.RESTORE); cmap = lookupCmap( font.cmap, @@ -741,6 +745,27 @@ function compileCharString(charStringCode, cmds, font, glyphId) { const NOOP = []; +class Commands { + cmds = []; + + add(cmd, args) { + if (args) { + if (args.some(arg => typeof arg !== "number")) { + warn( + `Commands.add - "${cmd}" has at least one non-number arg: "${args}".` + ); + // "Fix" the wrong args by replacing them with 0. + const newArgs = args.map(arg => (typeof arg === "number" ? arg : 0)); + this.cmds.push(cmd, ...newArgs); + } else { + this.cmds.push(cmd, ...args); + } + } else { + this.cmds.push(cmd); + } + } +} + class CompiledFont { constructor(fontMatrix) { if (this.constructor === CompiledFont) { @@ -757,8 +782,10 @@ class CompiledFont { let fn = this.compiledGlyphs[glyphId]; if (!fn) { try { - fn = this.compileGlyph(this.glyphs[glyphId], glyphId); - this.compiledGlyphs[glyphId] = fn; + fn = this.compiledGlyphs[glyphId] = this.compileGlyph( + this.glyphs[glyphId], + glyphId + ); } catch (ex) { // Avoid attempting to re-compile a corrupt glyph. this.compiledGlyphs[glyphId] = NOOP; @@ -793,16 +820,14 @@ class CompiledFont { } } - const cmds = [ - { cmd: "save" }, - { cmd: "transform", args: fontMatrix.slice() }, - { cmd: "scale", args: ["size", "-size"] }, - ]; + const cmds = new Commands(); + cmds.add(FontRenderOps.SAVE); + cmds.add(FontRenderOps.TRANSFORM, fontMatrix.slice()); + cmds.add(FontRenderOps.SCALE); this.compileGlyphImpl(code, cmds, glyphId); + cmds.add(FontRenderOps.RESTORE); - cmds.push({ cmd: "restore" }); - - return cmds; + return cmds.cmds; } compileGlyphImpl() {
src/display/api.js+2 −4 modified@@ -169,8 +169,8 @@ const DefaultStandardFontDataFactory = * pixels, i.e. width * height. Images above this value will not be rendered. * Use -1 for no limit, which is also the default value. * @property {boolean} [isEvalSupported] - Determines if we can evaluate strings - * as JavaScript. Primarily used to improve performance of font rendering, and - * when parsing PDF functions. The default value is `true`. + * as JavaScript. Primarily used to improve performance of PDF functions. + * The default value is `true`. * @property {boolean} [isOffscreenCanvasSupported] - Determines if we can use * `OffscreenCanvas` in the worker. Primarily used to improve performance of * image conversion/rendering. @@ -384,7 +384,6 @@ function getDocument(src) { }; const transportParams = { ignoreErrors, - isEvalSupported, disableFontFace, fontExtraProperties, enableXfa, @@ -2744,7 +2743,6 @@ class WorkerTransport { ? (font, url) => globalThis.FontInspector.fontAdded(font, url) : null; const font = new FontFaceObject(exportedData, { - isEvalSupported: params.isEvalSupported, disableFontFace: params.disableFontFace, ignoreErrors: params.ignoreErrors, inspectFont,
src/display/font_loader.js+74 −30 modified@@ -16,7 +16,7 @@ import { assert, bytesToString, - FeatureTest, + FontRenderOps, isNodeJS, shadow, string32, @@ -362,19 +362,13 @@ class FontLoader { class FontFaceObject { constructor( translatedData, - { - isEvalSupported = true, - disableFontFace = false, - ignoreErrors = false, - inspectFont = null, - } + { disableFontFace = false, ignoreErrors = false, inspectFont = null } ) { this.compiledGlyphs = Object.create(null); // importing translated data for (const i in translatedData) { this[i] = translatedData[i]; } - this.isEvalSupported = isEvalSupported !== false; this.disableFontFace = disableFontFace === true; this.ignoreErrors = ignoreErrors === true; this._inspectFont = inspectFont; @@ -440,35 +434,85 @@ class FontFaceObject { throw ex; } warn(`getPathGenerator - ignoring character: "${ex}".`); + } + if (!Array.isArray(cmds) || cmds.length === 0) { return (this.compiledGlyphs[character] = function (c, size) { // No-op function, to allow rendering to continue. }); } - // If we can, compile cmds into JS for MAXIMUM SPEED... - if (this.isEvalSupported && FeatureTest.isEvalSupported) { - const jsBuf = []; - for (const current of cmds) { - const args = current.args !== undefined ? current.args.join(",") : ""; - jsBuf.push("c.", current.cmd, "(", args, ");\n"); + const commands = []; + for (let i = 0, ii = cmds.length; i < ii; ) { + switch (cmds[i++]) { + case FontRenderOps.BEZIER_CURVE_TO: + { + const [a, b, c, d, e, f] = cmds.slice(i, i + 6); + commands.push(ctx => ctx.bezierCurveTo(a, b, c, d, e, f)); + i += 6; + } + break; + case FontRenderOps.MOVE_TO: + { + const [a, b] = cmds.slice(i, i + 2); + commands.push(ctx => ctx.moveTo(a, b)); + i += 2; + } + break; + case FontRenderOps.LINE_TO: + { + const [a, b] = cmds.slice(i, i + 2); + commands.push(ctx => ctx.lineTo(a, b)); + i += 2; + } + break; + case FontRenderOps.QUADRATIC_CURVE_TO: + { + const [a, b, c, d] = cmds.slice(i, i + 4); + commands.push(ctx => ctx.quadraticCurveTo(a, b, c, d)); + i += 4; + } + break; + case FontRenderOps.RESTORE: + commands.push(ctx => ctx.restore()); + break; + case FontRenderOps.SAVE: + commands.push(ctx => ctx.save()); + break; + case FontRenderOps.SCALE: + // The scale command must be at the third position, after save and + // transform (for the font matrix) commands (see also + // font_renderer.js). + // The goal is to just scale the canvas and then run the commands loop + // without the need to pass the size parameter to each command. + assert( + commands.length === 2, + "Scale command is only valid at the third position." + ); + break; + case FontRenderOps.TRANSFORM: + { + const [a, b, c, d, e, f] = cmds.slice(i, i + 6); + commands.push(ctx => ctx.transform(a, b, c, d, e, f)); + i += 6; + } + break; + case FontRenderOps.TRANSLATE: + { + const [a, b] = cmds.slice(i, i + 2); + commands.push(ctx => ctx.translate(a, b)); + i += 2; + } + break; } - // eslint-disable-next-line no-new-func - return (this.compiledGlyphs[character] = new Function( - "c", - "size", - jsBuf.join("") - )); - } - // ... but fall back on using Function.prototype.apply() if we're - // blocked from using eval() for whatever reason (like CSP policies). - return (this.compiledGlyphs[character] = function (c, size) { - for (const current of cmds) { - if (current.cmd === "scale") { - current.args = [size, -size]; - } - // eslint-disable-next-line prefer-spread - c[current.cmd].apply(c, current.args); + } + + return (this.compiledGlyphs[character] = function glyphDrawer(ctx, size) { + commands[0](ctx); + commands[1](ctx); + ctx.scale(size, -size); + for (let i = 2, ii = commands.length; i < ii; i++) { + commands[i](ctx); } }); }
src/shared/util.js+13 −0 modified@@ -1073,6 +1073,18 @@ function getUuid() { const AnnotationPrefix = "pdfjs_internal_id_"; +const FontRenderOps = { + BEZIER_CURVE_TO: 0, + MOVE_TO: 1, + LINE_TO: 2, + QUADRATIC_CURVE_TO: 3, + RESTORE: 4, + SAVE: 5, + SCALE: 6, + TRANSFORM: 7, + TRANSLATE: 8, +}; + export { AbortException, AnnotationActionEventType, @@ -1095,6 +1107,7 @@ export { DocumentActionEventType, FeatureTest, FONT_IDENTITY_MATRIX, + FontRenderOps, FormatError, getModificationDate, getUuid,
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-87hq-q4gp-9wr4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-34342ghsaADVISORY
- github.com/mozilla/pdf.js/commit/85e64b5c16c9aaef738f421733c12911a441cec6nvdWEB
- github.com/mozilla/pdf.js/pull/18015nvdWEB
- github.com/mozilla/pdf.js/security/advisories/GHSA-wgrm-67xf-hhpqnvdWEB
- github.com/wojtekmaj/react-pdf/commit/208f28dd47fe38c33ce4bac4205b2b0a0bb207fenvdWEB
- github.com/wojtekmaj/react-pdf/commit/671e6eaa2e373e404040c13cc6b668fe39839cadnvdWEB
- github.com/wojtekmaj/react-pdf/security/advisories/GHSA-87hq-q4gp-9wr4nvdWEB
News mentions
0No linked articles in our index yet.