Refuse a NULL inside a C-Octet String, which ends the field on the peer
Mirror / push (push) Successful in 4s
Test / lint (pull_request) Successful in 21s
Test / test (18) (pull_request) Successful in 29s
Test / test (20) (pull_request) Successful in 29s
Test / test (22) (pull_request) Successful in 31s
Test / test (24) (pull_request) Successful in 35s
Test / test (26) (pull_request) Successful in 30s

This commit is contained in:
2026-09-21 07:59:08 +02:00
parent 7db242e375
commit c1e0407942
7 changed files with 74 additions and 12 deletions
+12
View File
@@ -145,6 +145,18 @@ describe('parsing real PDUs', () => {
assert.equal(decode(pdu).params.source_addr, 'Kaffeé');
assert.ok(objToPdu({ cmdName: 'deliver_sm', params: { source_addr: '一' } }).err instanceof Error);
});
// The peer reads source_addr to the first NULL and every mandatory field behind it shifts, so an
// application forwarding a customer's sender id could have a PDU rewritten under it.
test('refuses an address carrying its own terminator', () => {
const smuggled = objToPdu({
cmdName: 'submit_sm',
params: { destination_addr: '46709771337', source_addr: '46701113311\u0000EVIL' },
});
assert.ok(smuggled.err instanceof Error);
assert.equal(smuggled.buffer, undefined);
});
});
describe('encoding submit_sm', () => {
+17
View File
@@ -83,6 +83,14 @@ describe('string (Octet String)', () => {
assert.ok(types.string.size('一').err instanceof Error);
assert.ok(types.string.write('一', Buffer.alloc(4), 0).err instanceof Error);
});
// Its length octet is what ends it, so unlike a C-Octet String it carries a NULL like any other.
test('carries a NULL octet, which its length octet already bounds', () => {
const target = Buffer.alloc(4);
assert.deepEqual(types.string.write('a\u0000b', target, 0), {});
assert.deepEqual(target, Buffer.from([3, 0x61, 0x00, 0x62]));
});
});
describe('cstring (C-Octet String)', () => {
@@ -128,6 +136,14 @@ describe('cstring (C-Octet String)', () => {
assert.ok(types.cstring.write('一', Buffer.alloc(4), 0).err instanceof Error);
});
// The field ends at its first NULL, so writing one smuggles a field boundary into the peer's
// parse: every mandatory field behind it shifts, under a command_length that counted the whole
// string.
test('refuses a NULL of its own rather than ending the field early', () => {
assert.ok(types.cstring.size('46701113311\u0000EVIL').err instanceof Error);
assert.ok(types.cstring.write('46701113311\u0000EVIL', Buffer.alloc(17), 0).err instanceof Error);
});
test('refuses a string with no terminator rather than running off the end', () => {
assert.ok(types.cstring.read(Buffer.from('abcd'), 0).err instanceof Error);
});
@@ -193,6 +209,7 @@ describe('text TLVs', () => {
assert.ok(types.tlv.cstring.size('一').err instanceof Error);
assert.ok(types.tlv.cstring.write('一', Buffer.alloc(4), 0).err instanceof Error);
assert.ok(types.tlv.cstring.write('a\u0000b', Buffer.alloc(4), 0).err instanceof Error);
});
});