Skip to content

Malformed OctetString INDEX length in SetRequest creates a row but response says NoSuchInstance #305

@hibrian827

Description

@hibrian827

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:

  1. Silently truncate the index in Mib.getRowIndexFromOid.
  2. Create a row in the table at the canonical (truncated-index) OID via Mib.addTableRow.
  3. Then look up the original malformed OID, which doesn't match the canonical row's storage location.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions