Skip to content

Memory-corruption fix + CI + ESP8266/ESP-01 hardening available in my fork (v3.1.23) — happy to PR if you want it #66

Description

@syntax1269

Hi,

I've been using Arduino_SNMP in ESP8266/ESP-01 projects for years now and i started to get deep into the code along the way. I've been maintaining an improved version in my fork and wanted to give you a heads-up. If you're interested, I'm happy to open a pull request — one big cumulative PR. And no pressure at all: if upstream would rather stay as-is, the fork lives on independently and is there for anyone who wants it:

https://github.com/syntax1269/Arduino_SNMP/tree/v3.3.1

This is a fully-compliant SNMPv2c Agent built for Arduino's, but will work on any OS, providing API code is written for packet serialization (See tests/mock.cpp for an example)

Current Version: 3.3.1

New since v3.1.5: a hardware-validated reliability campaign (v3.1.23–v3.1.25: pool double-release fix, loud tooBig rejection of over-cap GetBulk, pool-baseline freeze, 17 KB slot right-size), v3.2.0 derived pool sizing (the ASN pool auto-sizes at compile time from the number of handlers your sketch registers — no more one-size-fits-all arena), and v3.3.0 boot-time lock-in (the arena is claimed in the SNMPAgent constructor, before setup() and WiFi, so the heap can never fragment it).

Features

  • Full SNMPv2c Data Type support:
    • INTEGER int
    • STRING char[] / const char* (C-style strings, no std::string or Arduino String)
    • NULLTYPE
    • OIDTYPE const char* (dotted-decimal, e.g. ".1.3.6.1.4.1.5.0")
  • Complex data type support:
    • NETWORK ADDRESS
    • COUNTER32 uint32_t
    • GAUGE32 uint32_t
    • TIMESTAMP uint32_t
    • OPAQUE uint8_t*
    • COUNTER64 uint64_t
  • SNMP PDU Support
    • GetRequest
    • GetNextRequest
    • GetResponse (For SNMPv2c INFORM Responses only for now)
    • SetRequest
    • SNMPv2 Trap
    • GetBulkRequest
    • InformRequest
  • Deterministic memory (v3.2.0+)
    • Compile-time derived pool sizing — the arena scales with the handlers you register
    • Boot-time lock-in — one contiguous arena claimed before setup()/WiFi; zero per-packet heap traffic
    • Loud failure modes — over-cap GetBulk answers an RFC 3416 tooBig error PDU instead of silently truncating
    • Hardware-validated: ESP-01 30-minute soak campaigns, 0 pool alarms / 0 reboots / flat heap

It was designed and tested around an ESP32, but will work with any Arduino-based device that has a UDP object available. Optimized for ESP-01 (ESP8266) and other memory-constrained embedded targets — the library auto-tunes a reduced "TINY" profile on ESP8266 and was soak-tested on a 1 MB ESP-01 against upstream v2.1.0 (same sketch: flat heap, zero allocation failures, bounded deterministic RAM).

The example goes into detail around how to use, or look at src/SNMP_Agent.h for the API.

If you're coming from v1, most, but not all APIs are drop-in replaceable.
Some of the API's, especially around strings have changed. Look in SNMP_Agent.h for details.

If you're upgrading from v2.0/v2.1, note the 2.2.0 string model change below.

If you're upgrading from v2.2, v3.0.0 is source-compatible (no API changes) but fixes several critical BER TLV encoding/decoding bugs. Mandatory upgrade if you use GetBulk, large responses (length ≥ 128 bytes, especially exactly 256 bytes), or snmpbulkwalk.

If you're upgrading from v3.0.0 / v3.0.6 to v3.1.0: 100% source + wire compatible, zero API changes, zero breaking changes. v3.1.0 closes out the 4-phase zero-heap refactor (eliminates all remaining std::vector / std::deque / std::list from library source; replaces last make_shared temp-allocations with pool-allocated raw BER objects; drops 3 dead standard-container includes + 1 dead inline method that was pulling shared_ptr machinery per-TU). Flash is slightly smaller on every target (−0.71% average vs v3.0.0 baseline; largest win PlatformIO esp32dev −1.3% = −9.6 KB), BSS is deterministic +48.9 KB (linker-reported, no mid-packet fragmentation, tuneable down via SNMP_POOL_ASN_OBJECTS if you're on esp01_1m). Mandatory upgrade if you've ever seen ESP-01 heap-fragmentation panics after 30+ days of SNMP polling.


The headline: a real memory-corruption bug (fixed)

There's a use-after-free in the request path that can silently corrupt responses — and eventually crash the agent after days of uptime:

  1. handlePacket() explicitly called request.~SNMPPacket() and the destructor ran again at scope exit — every parsed request object was destroyed twice.
  2. ASNPool::release() had no double-release guard — the second release re-ran the destructor and decremented the pool's usedCount twice.
  3. With the counter under-reported, rawAlloc() could eventually hand a still-occupied pool slot to a new live object — silent data corruption with no error logs.

The visible symptom: GetBulk responses degrading to a single varbind when trap traffic overlaps request handling (the pool's transient slots are where request parse objects live). Both defects are fixed; the fix is proven on real hardware (details below).

Also in v3.1.23

  • Pool diagnostics: usedCountPeak high-water telemetry plus a one-shot DEBUG alarm that fires on any double release (it caught the bug live: DOUBLE RELEASE of slot 21 — the first transient slot, exactly where request parse objects live)
  • CI workflow (.github/workflows/test.yml): arduino-lint (specification mode), host Catch2 test suite, and a 4-way example-compile matrix (ESP8266 d1_mini + ESP32 × both examples) — all green
  • GCC -Wpedantic cleanups: removed extra ; after inline member functions (Ubuntu GCC treats these as errors with -Werror=pedantic; Apple clang doesn't flag them, which is how they slipped through)
  • Host test suite: 101 assertions / 10 test cases, all passing (the same UAF used to SIGSEGV the suite)
  • Static pool option (SNMP_POOLS_IN_BSS) and assorted RAM/flash footprint work for 1 MB parts like the ESP-01
  • Cumulative improvements from v2.2.0 onward (this is a large delta — upstream is at 2.1.0 and the fork has evolved through many tagged releases since; every tagged version in the fork has its own release notes)

How the fork got here — The Roadmap

Every step is tagged in the fork with its own release notes; the phases below summarize how v2.1.0 became v3.1.23:

Phase Versions Theme What landed
Baseline v3.1.5 Compliance arduino-lint clean (demos → extras/demos/); zero functional changes from upstream — the measured starting line
Safety (P0) v3.1.6 – v3.1.8 Stop the crashes callbacksCount overflow guard + virtual ValueCallback destructor; ASNPool zero-heap hot path with explicit nullptr error paths (3 silent NULL-deref/Exception-28 causes closed); trap OID ownership made explicit (no more new OIDType leaks in the trap API)
RAM (P1) v3.1.9 Smaller footprint SortableOIDType shrunk, pool slot size recalculated → +4 KB heap headroom (and −4 KB BSS with the optional SNMP_POOLS_IN_BSS static pool)
Flash (P2) v3.1.10 – v3.1.12 Smaller code duplicate PDU encode paths merged into one shared envelope builder; compile-time OID-literal strlen via template ctor; sort_oids() rewritten to lockstep BER decode → −320 B library ROM and 3–5× faster sort/walk with −232 B stack per comparator call
Hardening + DevX (P3) v3.1.13 – v3.1.14 Correct-by-construction API printAllOIDsTo(Print&) roster helper; SNMP community truncation auth-bypass closed (len > cap now rejects instead of silently truncating); UDP.begin() return-value checked; Counter setOccurred only on real value change
Interim (v3.1.15 – v3.1.22 internal) Build ID + hardware campaign LIBRARY_VERSION + SNMPAgent::getVersion() so sketches/sysDescr always report the exact build; example banner updates; extensive ESP-01 hardware validation (details in the table below). These intermediate numbers were used during on-hardware testing but were never tagged — the first public tag after v3.1.14 is v3.1.23
Correctness (this release) v3.1.23 Fix the latent UAF the double-destroy + double-release fix described above, plus pool telemetry and full CI

Net effect since the v3.1.5 baseline: +4 KB free heap, 0 trap-path leaks (worst case was O(N × ~170 B)), 3/3 Exception-28 causes closed, auth bypass closed, 100% of polymorphic-delete UB eliminated, −320 B library ROM, ~3–5× faster OID sort/walk, and the native suite holding at 101/101 with zero regressions across all eleven releases.

Verification (not just claimed — measured)

Check Result
Host suite (make ci-test, GCC, -Werror) 101/101 assertions, 10/10 cases
Example compiles (ESP8266 d1_mini + ESP32 × 2 examples) 4/4 green
ESP-01 on hardware, full RFC1213 system group 8/8 PASS
ESP-01 GETBULK across trap boundaries 14/14 varbinds + clean EOM (was: single varbind)
39-minute soak (7,664 ops: GET/GETNEXT/GETBULK/SET loops) 0 alarms, 0 leaks (heap flat), 0 reboots, pool peak 76/76 recovering cleanly
Flash footprint (ESP-01, 1 MB) 28% — well within budget

If you'd like a PR

Say the word and i will pull one cumulative PR that includes all the fixes and enhancements. If you'd rather not, that's completely fine — the fork stays available at the link above for anyone that wants to use this new version.

Either way, thanks for the excellent library — it's been the backbone of several projects over the years.

— syntax1269

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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