Skip to content

Pre-auth stack buffer overflow in S7 Write handler #12

Description

@entropy1337

[CWE-787/CWE-121] Pre-auth stack buffer overflow in S7 Write handler — snap7 server

Finding ID: SNAP7-03 Severity: Critical (unauthenticated remote, potential RCE)
Component: snap7 S7 server (libsnap7) — TS7Worker::PerformFunctionWrite()

Summary

An unauthenticated remote attacker who can open a TCP connection to a snap7 S7
server (default port 102/tcp) can overflow a fixed 20-element pointer array on
the worker thread's stack by sending a single S7 Write request whose item
count byte is larger than 20. The write handler loops over the attacker's
ItemsCount (a full 0–255 byte) with no upper bound, storing a pointer into
ReqData[c] for each iteration; the array ReqData holds only MaxVars = 20
entries. Overwriting past it corrupts adjacent stack memory (the response
buffer, saved registers, return address) with attacker-influenced pointer
values, giving a remote crash and a plausible path to code execution.

The sibling Read handler caps this exact value (if (ReqParams->ItemsCount>MaxVars) ReqParams->ItemsCount=MaxVars;);
the Write handler is missing that clamp.

Affected Version

SCADACS/snap7 at commit f6ff90317ca5d54250f4dcd29209689a74e26d82 (S7 server core
src/core/s7_server.cpp; codebase descends from Snap7 1.4.x). Any build that
exposes TSnap7Server / Srv_Start*.

Reachability

REACHABLE, unauthenticated. S7comm has no password/auth in the base protocol.
Path: TCP connect → COTP Connection Request/Confirm → S7 request PDU with
function pduFuncWrite (0x05).
ExecuteRecv → IsoPerformCommand → PerformPDURequest (case pduFuncWrite) → PerformFunctionWrite.
No registered data area is required — the overflow occurs in stage-1 parameter
setup, before any area lookup.

Technical Details

src/core/s7_types.h:

const int MaxVars = 20;
typedef PReqFunWriteDataItem TReqFunWriteData[MaxVars];   // 20 pointers
struct TReqFunWriteParams { byte FunWrite; byte ItemsCount; TReqFunWriteItem Items[MaxVars]; };

src/core/s7_server.cpp, PerformFunctionWrite():

TReqFunWriteData ReqData;                 // 20-element stack array (line 844)
...
ItemsCount = ReqParams->ItemsCount;       // attacker byte 0..255, NO clamp (line 860)
for (c = 0; c < ItemsCount; c++) {
    ReqData[c] = PReqFunWriteDataItem(pbyte(PDUH_in)+StartData);  // OOB write at c>=20 (line 864)
    ...
}

CheckPDU_in() only verifies ParLen + DataLen + ReqHeaderSize == PayloadSize;
it never bounds ItemsCount, so ItemsCount = 0xFF passes framing validation.

Data flow: TCP bytes → S7 PDU param byte ItemsCount → loop bound → ReqData[c]
stack write past a 20-entry array (160 bytes) → up to 235 pointer slots (~1.8 KB)
of stack corruption.

Proof of Concept

poc/poc_snap7_write_overflow.cpp starts the real TSnap7Server (built with
-fsanitize=address) on 127.0.0.1, performs the COTP handshake, and sends one
12-byte S7 write PDU with ItemsCount = 0xFF. AddressSanitizer output
(poc/crash/SNAP7-03_write_asan.txt):

ERROR: AddressSanitizer: stack-buffer-overflow ... WRITE of size 8
  #0 TS7Worker::PerformFunctionWrite() s7_server.cpp:864
  #1 TS7Worker::PerformPDURequest(int&)  s7_server.cpp:336
  #2 TS7Worker::IsoPerformCommand(int&)  s7_server.cpp:307
  #3 TIsoTcpWorker::ExecuteRecv()
 This frame has 3 object(s):
    [32, 192) 'ReqData'  <== Memory access at offset 192 overflows this variable
    [256, 4352) 'Answer'

Crashing input (raw S7 PDU): poc/corpus/seed_write_overflow.bin
(32 01 00 00 00 00 00 02 00 00 05 FF). Also reproduced by the AFL harness
(poc/fuzz_s7_pdu).

Impact

CVSS v3.1: 9.8 CriticalAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H.
Unauthenticated remote stack corruption in a PLC-facing server that frequently
runs with high privilege. Minimum impact is reliable remote crash (DoS of the
PLC/gateway); the overwritten values are attacker-influenced heap pointers, so
control-flow hijack (RCE) is plausible depending on stack layout / mitigations.

Suggested Remediation

Clamp the count before the loop, mirroring the Read handler:

if (ReqParams->ItemsCount > MaxVars) ReqParams->ItemsCount = MaxVars;
ItemsCount = ReqParams->ItemsCount;

and additionally validate that each item's declared offset/length stays within
the received PDU.

Disclosure Timeline

  • Discovered: 2026-07-03 (local static analysis + ASan PoC)
  • Vendor notified: PENDING — operator to contact maintainer (no contact made)
  • CVE requested: pending
  • Public disclosure target: +90 days after vendor contact

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions