SAP Security Note 3747649 | CVSS 3.0 base score 10.0 (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) | Component BC-CST-DP | SAP Security Patch Day, 8 September 2026
At a glance
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:HSummary
An SAP Internet Communication Manager, or a standalone SAP Web Dispatcher, inspects every HTTP request for the traceability header sap-passport. That inspection runs before authentication. The value is a hex-encoded Extended Passport stream. eppDeserialize decodes it, takes a length field straight off the wire, and then reuses that same number as the write limit for a fixed-size buffer in its own stack frame. One request runs past that buffer, into the saved registers and the saved return address above it.
Nothing gates the path, not a credential, not a click, not a profile parameter. Reaching an HTTP port over TCP is the whole prerequisite. That is why SAP rates the issue 10.0.
The proven effect is immediate loss of availability. One anonymous request kills the worker that handles it. The process logs a segmentation fault, and the service is back seconds later. Both the length and the content of the overwrite come from the request. Under a debugger, on a standalone Web Dispatcher with a known load base, eppDeserialize returned to a header-supplied address. That is a weaponization path rather than a demonstration against a hardened target. A production binary ships as position-independent code with full RELRO, the operating system randomizes its load address, and anything past a crash has to defeat that unknown module base first. The automatic restart makes repeated attempts cheap. Confidentiality and integrity carry High on that path, because the process runs under the operating system account that owns the SAP instance.
The note repairs three length-validation defects in the same deserializer. Besides the overflow above, an unsigned subtraction underflows when an item is shorter than its own header. A one-sided check on a segment offset lets the parser walk backward out of the stream.
Affected products and versions
The defective code is the Extended Passport library, and the fault sits in eppDeserialize. The class is CWE-121, a stack buffer overflow. That library ships inside disp+work and icman, which provide the ICM in every ABAP-based system, and inside sapwebdisp, the standalone Web Dispatcher. Both tiers carry the same defect. SAP Note 3747649 gives the required level for each. The teardown below comes from sapwebdisp, and a live ICM reproduced the same crash. The split matters for planning. Patching the application servers leaves a defective Web Dispatcher standing in front of them, and a patched Web Dispatcher does nothing for an ICM port that is reachable on its own.
The comparison below uses kernel 9.16, matching sapwebdisp patch level 71 against patch level 100 function by function. The corrections are present at PL100 and absent at PL71. PL50 and PL71 both crashed. No level in between came under examination, so treat every level below PL100 on this line as vulnerable unless SAP Note 3747649 says otherwise. The same crash occurs on an S/4HANA appliance running kernel 9.16 PL50, in a real ICM, not a sandbox. Other kernel lines carry the defect too, each with its own boundary. SAP Note 3747649 is authoritative for the full list.
Technical detail
Reachability
The entry point is HttpHandleDtrace() in http_plg.c of the PL71 binary. The ICP plugin chain reaches it during header processing. When no sap-dtrace header is present, which is the normal case, the code takes the sap-passport branch:
lea rdi, aSapPassport_0 ; "sap-passport"
call IctIHttpGetHeaderFieldByName
jz loc_no_header ; header absent, nothing to do
mov rsi, [r14+40h] ; header value
call IcmPlDtraceReadSapPassport
The path consults no authentication state, and no profile switch has to be set for it to run. An absent header does nothing. An empty one takes a short path that logs sap-passport header is empty.
The decode happens in IcmDtraceReadSapPassport() in icdtrace.c. The header text is hex, two characters per byte, converted with strtolU16 at base 0x10. Whether the decoded stream lands on the stack or the heap makes no difference. The overflow happens further down, in the frame of eppDeserialize itself. On a live system the ICM reads the header and decodes the passport on requests it later rejects as unauthenticated. No logon step stands between the network and the defective code.
What a passport looks like
A passport is a fixed-size header followed by an optional chain of variable-part segments, each carrying a count of items. The header holds an eyecatcher, a version byte, a total length, and fixed-width text fields such as component name, user id and action. An item is a short record, a fixed header followed by a payload. Two of its header fields do the damage. A one-byte selector picks the conversion routine, and one of its values targets a fixed-size stack buffer; a 16-bit length covers the whole record, and the payload size derives from it. The format never compares anything against the size of that destination. Field order, frame offsets and absolute addresses stay out of this write-up. Nothing below needs them.
The defect
For a type 4 item the deserializer derives a payload length from the wire. It then passes that length as both the source length and the maximum number of output units:
cmp r8b, 4 ; data type 4, UTF-8 string
jnz short loc_next_item
mov r8d, r9d ; source length, from the wire
mov esi, r9d ; maximum OUTPUT units, from the wire
mov rdi, [rbp+buf] ; destination: a fixed 128-byte stack local
call Utf8nToUcnOverlap_2
The rest of the function does not work that way. The fixed header fields go through the same helper, and every one of those call sites loads a constant into esi, 0x20 for the component name, 0x28 for the action. The cap is compiled in, so those sites are safe. Only the item path substitutes a number the sender chose.
The helper derives its own limit from the cap it receives, which makes the caller’s number the memory-safety bound:
movsxd rsi, esi ; maxUnits, attacker supplied
add rsi, rsi ; output limit in bytes = 2 * maxUnits
lea rdx, [rdi+rsi] ; end of destination
The checks that are present
The parser is not careless everywhere. It verifies both eyecatchers and range checks the total length. It requires the supplied data to be at least as long as that length, and confirms that each item still lies inside the stream. Every one of those constrains how far the parser reads, not how much the conversion writes. That same trusted number, once it has passed a read check, then serves as a write bound.
eppDeserialize is compiled without a canary of its own. Its caller installs one, but that check runs only after eppDeserialize has already returned through the overwritten slot, so the guard sits on the wrong frame and the transfer has happened before it fires.
Character conversion never cuts the write short. The terminal routine is the substituting variant, so invalid input becomes U+FFFD and the loop keeps going, and an embedded zero byte converts to a zero output unit that the loop steps straight over. Whatever the bytes are, the write runs to the full requested count.
What the overwrite reaches
The frame layout supplies the rest. The conversion writes forward from a 128-byte buffer low in the frame, and above that buffer sit the callee-saved registers, the saved frame pointer and the return address. The output count comes from the wire. Nothing consults the size of the destination. One item can run past the buffer, through the saved registers, into the return address and on into the caller’s frame. The length field is 16 bits wide, so the format permits a requested distance far larger than the one the write has to travel. Under a debugger the fault lands on the return from eppDeserialize itself, with request data occupying the saved control words of that frame. That observation, not the crash alone, is what carries the confidentiality and integrity ratings. The exact offsets are withheld here.
The two sibling defects
The item loop derives the payload size by subtracting the seven-byte item header. It never establishes that the declared length reaches that header size:
movzx r9d, ax ; item length from the wire
cmp r15, r14 ; next item still inside the stream?
sub r9, 7 ; payload = length - 7, unsigned
A declared length below seven wraps the 32-bit result to roughly four gigabytes, and that number becomes the conversion cap. The second sibling is the value locating the variable-part segments. The parser sign-extends it and compares it only against the end of the stream:
movsx rax, ax ; sign extension of the offset
add rax, rbx
cmp rax, r14 ; only "above the end" is rejected
ja loc_reject
The one-sided, unsigned comparison lets a negative offset through, and the result is a pointer before the stream. The parser then reads a segment header out of unrelated memory, and if an eyecatcher happens to sit there, it copies items that never belonged to the request. That out-of-bounds read is one half of a possible disclosure route. The other half is the error reflection path in HttpHandleDtrace, which echoes passport text into traces. No test here showed foreign memory reaching dev_icm or dev_webdisp, so the read stands as a real defect with an unproven consequence.
What the patch changes
At PL100 the function has moved and carries three new comparisons, one for each defect. The variable-part offset must now be non-negative. The item length must be at least its own seven-byte header. The code now caps the type 4 payload before the conversion runs:
test ax, ax
js loc_reject ; new: negative offset rejected
cmp cx, 6
jbe short loc_reject ; new: length below the 7-byte item header rejected
cmp r9, 40h ; new: payload capped at 64 units
ja loc_reject
Sixty-four UCS-2 units is exactly 128 bytes, which is the size of the destination buffer. The patched code supplies the bound the old code lacked, because everything above that boundary was writable from the network. SAP describes the correction as proper length validation for all externally supplied length fields processed during EPP deserialization, and that wording maps one to one onto the three defects in the diff.
Verification for defenders
Read the patch level, because that is the only reliable test. It comes from the kernel package of the instance, for example with disp+work -V on an application server. A standalone Web Dispatcher needs a separate read, because it carries its own copy of the library. Take the release and patch level that the sapwebdisp binary itself reports, from its dev_webdisp trace or from the Web Dispatcher administration page. Compare each figure against the level SAP Note 3747649 requires for that kernel line. On kernel 9.16 the boundary observed here is patch level 100, and PL50 and PL71 both crashed. Other lines have their own boundary, so take the required level from the note and not from this figure.
Inventory every binary that terminates SAP HTTP traffic, not only the internet-facing ones. Web Dispatchers, application server ICM ports and test instances all carry the same library. The note also lists a companion patch for the SAP host management component; apply that alongside the kernel update. SAP Note 3759472, CVE-2026-58240, corrects a separate unauthenticated flaw in the message server of the same kernel. It shipped on the same patch day, so plan one kernel patch window and check both required levels together.
Do not try to confirm this one by sending a passport. The patch works by rejecting oversized and undersized items rather than by answering differently to normal traffic, so a probe either tells you nothing or kills a worker. SAP Note 3756304 offers a workaround, though only where a standalone Web Dispatcher terminates HTTP. Kernel ICM ports have no clean workaround at all. Until you update the kernel, restrict which networks can reach those ports.
Detection
In dev_icm or dev_webdisp, a successful attempt produces a segmentation fault whose stack cannot be unwound, because the return address no longer points at code:
[Thr ...] ------------------ C-STACK ----------------------
C-STACK not possible, because stack unwinding starting
from a non-source address (causing the SEGV) is impossible.
...
[Thr ...] ***LOG Q0E=> SigIGenAction, signal ( 11) [sigux.c 1962]
[Thr ...] caught SIGSEGV (11)
On the appliance used here the ICM restarted within 3 to 9 seconds, logging a banner such as *** ICM up and operational, after which the service answered normally again. Treat repeated worker deaths carrying that message as an attack in progress and not as instability, particularly when no application error sits underneath them. Keep the trace files as evidence too. They record the reflected passport text of the request.
At the network layer, alert on the sap-passport header when it arrives from an untrusted source, since legitimate passports come from SAP clients inside a traced system. Start with anonymous sap-passport headers above roughly 1.7 KB of hex, and baseline your own traffic before fixing that threshold. The format narrows the rule further. The decoded stream begins and ends with the four-byte marker *TH*, that is the bytes 2A 54 48 2A. The parser compares that marker as the little-endian dword 0x2A48542A. The header value is hex text at two characters per byte, so its length is always even. A rule can require a leading 2a54482a in either case and treat an odd-length value as malformed. An attempt that stops short of crashing the worker may leave nothing behind, however, so an absence of crash entries proves nothing.
References
- SAP Security Note 3747649, the security note itself
- SAP Note 3776034, the FAQ that accompanies Note 3747649
- SAP Note 3756304, the workaround for HTTP terminated by a standalone Web Dispatcher
- SAP Note 908097 and SAP Note 3115889, Web Dispatcher patching and deployment
- CVE-2026-44756 in the CVE catalog
- SAP Security Patch Day September 2026, the full cycle this note shipped in
- SAP Note 3759472, CVE-2026-58240, the message server flaw corrected in the same kernel on the same day
The findings above come from two sapwebdisp binaries read side by side, PL71 and PL100, and from a live ICM that crashed on request. This write-up describes the vulnerability and the correction as they appear in those shipped kernel binaries.
This is one of four HotNews notes corrected on the September 2026 patch day. The full cycle, including the other 19 notes, is covered in the SAP Security Patch Day September 2026 advisory.
Know which of your systems is still on a vulnerable level
Kernel flaws like this one are closed by a patch level, not by a configuration change, so the work is knowing where you stand. The RedRays Security Platform inventories your SAP hosts, reports missing SAP Security Notes per system and tracks the gap until it closes.
See the RedRays Security PlatformThis advisory summarises publicly relevant facts about an SAP Security Note that SAP has already released, together with an analysis of the shipped correction. It is not a substitute for the note itself; customers should always refer to the original note in the SAP Support Portal for authoritative guidance on affected releases and required patch levels. No exploit code is published. RedRays is an independent SAP security vendor and is not affiliated with SAP SE.




