Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@ function validateIdentifier(name) {
}
}

// Per protobuf spec a tag is an integer in [1, 2^29 - 1] (accepting numeric strings too, as .proto text parser emits them).
// Validated to prevent code injection as Field tags are interpolated directly into generated JS
function validateTag(tag) {
const n = typeof tag === 'string' ? Number(tag) : tag;
if (!Number.isInteger(n) || n < 1 || n > 0x1fffffff) {
throw new Error(`Invalid protobuf field tag: ${JSON.stringify(tag)}`);
}
}

function buildContext(proto, parent, mapEntryField) {
const obj = Object.create(parent);
obj._proto = proto;
Expand All @@ -233,6 +242,7 @@ function buildContext(proto, parent, mapEntryField) {
for (const field of proto.fields ?? []) {
validateIdentifier(field.name);
if (field.oneof) validateIdentifier(field.oneof);
validateTag(field.tag);
}
for (const valueName of Object.keys(proto.values ?? {})) validateIdentifier(valueName);

Expand Down
39 changes: 39 additions & 0 deletions test/compile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,42 @@ test('rejects proto schemas with invalid identifiers', () => {

assert.equal(globalThis.__pbfPwned, undefined);
});

test('rejects proto schemas with invalid field tags', () => {
const makeProto = tag => ({
syntax: 3,
package: null,
imports: [],
enums: [],
messages: [{
name: 'Foo',
enums: [],
messages: [],
extensions: null,
fields: [{
name: 'a',
type: 'string',
tag,
map: null,
oneof: null,
required: false,
repeated: false,
options: {}
}]
}]
});

// code injection via a string tag interpolated into the generated source
assert.throws(() => compile(makeProto('(globalThis.__pbfTagPwned = true, 1)')), /Invalid protobuf field tag/);
assert.equal(globalThis.__pbfTagPwned, undefined);

// out-of-range and non-integer tags
assert.throws(() => compile(makeProto(0)), /Invalid protobuf field tag/);
assert.throws(() => compile(makeProto(-1)), /Invalid protobuf field tag/);
assert.throws(() => compile(makeProto(1.5)), /Invalid protobuf field tag/);
assert.throws(() => compile(makeProto(0x20000000)), /Invalid protobuf field tag/);

// valid numeric and numeric-string tags (the .proto text parser emits strings) still compile
assert.doesNotThrow(() => compile(makeProto(1)));
assert.doesNotThrow(() => compile(makeProto('2')));
});
Loading