A SetRequest whose OID suffix encodes a non-implied OctetString index with a length byte larger than the bytes actually provided causes the agent to silently truncate the index, create a row at the canonical (truncated) OID, and then build the SetResponse as NoSuchInstance for the original OID. So the response says the SET failed, but the row was created — at a different OID than the one the request named.
Tested against master at commit 33d015b3f5d3e2a288cfdd7c5e188091115047d4 (net-snmp@3.26.1). Self-contained reproducer attached as a zip below.
What we observed
A SetRequest whose OID suffix encodes a non-implied OctetString index with a length byte larger than the bytes actually provided causes the agent to:
- Silently truncate the index in
Mib.getRowIndexFromOid.
- Create a row in the table at the canonical (truncated-index) OID via
Mib.addTableRow.
- Then look up the original malformed OID, which doesn't match the canonical row's storage location.
- Build the SetResponse as
NoSuchInstance for the requested OID.
So the SetResponse reports the SET failed, but the row was created — at a different OID than the one the request named.
Where it lives in index.js
In Mib.getRowIndexFromOid (line 4578), for non-implied OctetString index parts:
length = addressRemaining.shift(); // 4603 — read claimed length
value = addressRemaining.splice(0, length); // 4605 — splice; silently returns < length
value = value.map(c => String.fromCharCode(c)).join(""); // 4606 — accept truncated
Array.prototype.splice returns whatever's left when length > addressRemaining.length; there's no value.length === length check after.
Then in the SET path:
- Line 5167 calls
getRowIndexFromOid and gets the truncated index back.
- Line 5203 invokes
this.mib.addTableRow(...) with that index — the row is committed at the canonical (truncated) OID.
- Line 5207 calls
this.mib.lookup(oid) with the original malformed OID — the lookup misses.
- Lines 5300–5313 translate the miss into
NoSuchInstance in the response.
There's no rollback between 5203 and 5207, so the row stays.
Reproducer
A self-contained Node script that starts an in-process agent, registers a writable RowStatus table, sends one malformed SET and one canonical SET, and prints a narrated BUG REPRODUCED block when the malformed SET succeeds in creating a row despite returning NoSuchInstance.
poc.zip
Output excerpt:
SetResponse for malformed OID:
{"oid":"1.3.6.1.4.1.8072.9997.1.3.5.65","result":"ok","varbinds":[{...,"varbindError":"NoSuchInstance: 1.3.6.1.4.1.8072.9997.1.3.5.65"}]}
getTableCells('octetIndexTable'):
[[["A"],7,4]]
Suggested fix
In Mib.getRowIndexFromOid, validate variable-length index encodings after the splice:
- For non-implied OctetString and OID index parts: require
value.length === length; reject otherwise.
- For fixed-width IpAddress: require at least 4 components remain.
In the SET path, ensure the error propagates so addTableRow (line 5203) is not called when index decoding fails — the side effect needs to be removed, not just the response error.
A regression-test predicate: send the malformed SET, assert getTableCells is empty afterwards; send a canonical SET to the same column, assert the canonical row is created normally.
A SetRequest whose OID suffix encodes a non-implied OctetString index with a length byte larger than the bytes actually provided causes the agent to silently truncate the index, create a row at the canonical (truncated) OID, and then build the SetResponse as
NoSuchInstancefor the original OID. So the response says the SET failed, but the row was created — at a different OID than the one the request named.Tested against
masterat commit33d015b3f5d3e2a288cfdd7c5e188091115047d4(net-snmp@3.26.1). Self-contained reproducer attached as a zip below.What we observed
A SetRequest whose OID suffix encodes a non-implied OctetString index with a length byte larger than the bytes actually provided causes the agent to:
Mib.getRowIndexFromOid.Mib.addTableRow.NoSuchInstancefor the requested OID.So the SetResponse reports the SET failed, but the row was created — at a different OID than the one the request named.
Where it lives in
index.jsIn
Mib.getRowIndexFromOid(line 4578), for non-implied OctetString index parts:Array.prototype.splicereturns whatever's left whenlength > addressRemaining.length; there's novalue.length === lengthcheck after.Then in the SET path:
getRowIndexFromOidand gets the truncated index back.this.mib.addTableRow(...)with that index — the row is committed at the canonical (truncated) OID.this.mib.lookup(oid)with the original malformed OID — the lookup misses.NoSuchInstancein the response.There's no rollback between 5203 and 5207, so the row stays.
Reproducer
A self-contained Node script that starts an in-process agent, registers a writable RowStatus table, sends one malformed SET and one canonical SET, and prints a narrated
BUG REPRODUCEDblock when the malformed SET succeeds in creating a row despite returningNoSuchInstance.poc.zip
Output excerpt:
Suggested fix
In
Mib.getRowIndexFromOid, validate variable-length index encodings after the splice:value.length === length; reject otherwise.In the SET path, ensure the error propagates so
addTableRow(line 5203) is not called when index decoding fails — the side effect needs to be removed, not just the response error.A regression-test predicate: send the malformed SET, assert
getTableCellsis empty afterwards; send a canonical SET to the same column, assert the canonical row is created normally.