1. Overview

  • Unpatched Binary: atapi_unpatched.sys
  • Patched Binary: atapi_patched.sys
  • Overall Similarity Score: 0.9583
  • Diff Statistics: 51 matched functions (33 identical, 18 changed), 0 unmatched functions in either direction.
  • Verdict: The patch relocates an ATA/ATAPI device-status check. In the unpatched build both callers of AtapiNoDeviceConnected test the status byte for 0xFF (floating bus / no device) immediately before the call and skip the call when it is 0xFF. The patched build removes those two caller-level pre-checks and instead handles 0xFF inside AtapiNoDeviceConnected itself. For every status value the observable result — the returned status code and the byte written to the IRB — is identical in both builds. This is a code consolidation (defense-in-depth relocation), not a fix for any reachable defect.

2. Vulnerability Summary

  • Severity: None (informational)
  • Vulnerability Class: No security-relevant change — guard relocation / refactor. No CWE applies.
  • Affected Function: AtapiNoDeviceConnected (unpatched @ 0x1C0001FC8, patched @ 0x1C0001FD8) and its two callers AtapiSendAtaIdentify and AtapiSendAtaCommand.

What actually changed: AtapiNoDeviceConnected decides whether a device is absent by reading the ATA status register (passed by pointer in a2). The unpatched function checks for 0x7F (BSY+DRQ) and 0x00 and has no explicit 0xFF branch. The patched function adds one line at entry: if (*a2 == 0xFF) return true; (device absent).

The two — and only two — callers of this function are AtapiSendAtaIdentify and AtapiSendAtaCommand. In the unpatched build each caller performs its own 0xFF test on the exact status byte it is about to hand to the callee, and short-circuits before calling when the byte is 0xFF:

  • AtapiSendAtaIdentify unpatched: if ( v7 == -1 || AtapiNoDeviceConnected(...) ) result = 5; — the v7 == -1 term (0xFF) skips the call.
  • AtapiSendAtaCommand unpatched: cmp al, 0FFh; jz at 0x1C000226C skips the call and sets result 6.

The patched build deletes both of these pre-checks and lets AtapiNoDeviceConnected report 0xFF back to the caller. Because the callee now returns true for 0xFF, the caller takes the same branch it took before (result 5 for Identify, result 6 for Command), and writes the same status byte to *(Irb+3). The behavior is byte-for-byte equivalent.

Why there is no reachable defect in the unpatched build: In both callers the byte tested by the caller's 0xFF pre-check is the identical stack byte ([rsp+arg_0] / local PortUchar) that AtapiNoDeviceConnected dereferences through a2; there is no re-read of the port between the pre-check and the call. Consequently, in the unpatched build AtapiNoDeviceConnected is never entered with an initial status of 0xFF — the callers filter it first. The callee's lack of an internal 0xFF entry branch was therefore unreachable, and moving that branch into the callee changes no observable outcome.

The busy-wait loop inside AtapiNoDeviceConnected re-reads the status register and can observe a 0xFF value that appears only after entry (a device removed mid-poll). That in-loop case is handled identically in both builds: the loop exits and the code reaches cmp al, 7Fh; setz dil (return PortUchar == 127;), which yields 0 for a 0xFF value. The patched entry check guards only the initial status, not the in-loop re-read, so it does not alter this path. Any concern about a device removed during the poll is therefore present equally in both builds and is not addressed by this patch.

3. Pseudocode Diff

Decompiled logic of the callee. The only difference is the added entry line in the patched version:

// AtapiNoDeviceConnected — UNPATCHED @ 0x1C0001FC8
bool AtapiNoDeviceConnected(__int64 a1, UCHAR *a2, char a3, char a4) {
    UCHAR v4 = *a2;
    char  v5 = 0;
    if ( v4 == 127 || (a3 != 0 && v4 == 0) ) {
        int v10 = 0;
        v5 = 1;
        do {                                   // busy-wait, up to 3 re-reads
            AtaPortStallExecution(0x1388u);
            PortUchar = AtaPortReadPortUchar(*(PUCHAR *)(a1 + 72));
            *a2 = PortUchar;
            if ( PortUchar != v4 ) break;
            ++v10;
        } while ( v10 < 3 );
        if ( a3 == 0 || PortUchar != 0 )
            return PortUchar == 127;           // 0xFF here -> returns 0 (same in both builds)
        // ... a3-set path ...
    }
    return v5;
}

// AtapiNoDeviceConnected — PATCHED @ 0x1C0001FD8
bool AtapiNoDeviceConnected(__int64 a1, UCHAR *a2, char a3, char a4) {
    UCHAR v4 = *a2;
    char  v5 = 0;
    if ( *a2 == 0xFF )                          // ADDED: report device absent
        return true;
    if ( v4 == 127 || (a3 != 0 && v4 == 0) ) {
        // ... identical to unpatched ...
    }
    return v5;
}

Matching caller change (AtapiSendAtaIdentify), showing the pre-check that was removed:

// UNPATCHED @ 0x1C00020C0 — caller filters 0xFF before the call
if ( v7 == -1                                  // v7 == 0xFF  -> skip callee
  || (v8 = AtapiNoDeviceConnected(a1, &v11, ...), v7 = v11, v8) )
    result = 5;

// PATCHED @ 0x1C00020E0 — no pre-check; callee handles 0xFF
v5 = AtapiNoDeviceConnected(a1, &PortUchar, ...);
v7 = PortUchar;
if ( v5 )
    result = 5;

AtapiSendAtaCommand receives the mirror-image change: its cmp al, 0FFh; jz pre-check is removed and it relies on the callee's return value; result 6 and the *(Irb+3)=status write are unchanged.

4. Assembly Analysis

The patched callee inserts a single 0xFF test at entry. Real instructions from AtapiNoDeviceConnected in each build:

; UNPATCHED AtapiNoDeviceConnected @ 0x1C0001FC8
0x1C0001FE5  mov     bpl, [rdx]        ; status = *a2
0x1C0001FE8  xor     dil, dil          ; v5 = 0
0x1C0001FF7  cmp     bpl, 7Fh          ; 0x7F check
0x1C0001FFB  jz      0x1C000200F       ; -> busy-wait
0x1C0001FFD  test    r8b, r8b          ; a3 == 0 ?
0x1C0002000  jz      0x1C0002098       ; -> return v5
0x1C0002006  test    bpl, bpl          ; status == 0 ?
0x1C0002009  jnz     0x1C0002098       ; -> return v5   (0xFF ends here in unpatched)
; ... busy-wait loop and post-loop 'cmp al,7Fh; setz dil' unchanged vs patched ...

; PATCHED AtapiNoDeviceConnected @ 0x1C0001FD8
0x1C0001FF5  mov     bl, [rdx]         ; status = *a2
0x1C0001FF7  xor     dil, dil          ; v5 = 0
0x1C0002006  cmp     bl, 0FFh          ; ADDED: floating-bus test
0x1C0002009  jnz     0x1C0002015       ; not 0xFF -> normal checks
0x1C000200B  mov     edi, 1            ; 0xFF -> return true (device absent)
0x1C0002010  jmp     0x1C00020B3
0x1C0002015  cmp     bl, 7Fh           ; original 0x7F check
0x1C0002018  jz      0x1C000202B

Corresponding removed caller pre-checks (present only in the unpatched build):

; UNPATCHED AtapiSendAtaIdentify @ 0x1C00020C0
0x1C0002164  cmp     dl, 0FFh          ; caller 0xFF pre-check
0x1C0002167  jz      0x1C00021DA       ; -> result 5, skip callee
0x1C0002174  call    AtapiNoDeviceConnected

; UNPATCHED AtapiSendAtaCommand @ 0x1C0002200
0x1C000226C  cmp     al, 0FFh          ; caller 0xFF pre-check
0x1C000226E  jz      0x1C00024D1       ; -> result 6, skip callee
0x1C000227F  call    AtapiNoDeviceConnected

In the patched build (AtapiSendAtaIdentify @ 0x1C00020E0, AtapiSendAtaCommand @ 0x1C0002210) these cmp ..., 0FFh pre-checks are absent and the call is reached directly (0x1C0002187 and 0x1C0002286 respectively).

5. Trigger Conditions

Not applicable. There is no reachable defect that differs between the two builds. In the unpatched build the status byte handed to AtapiNoDeviceConnected is filtered for 0xFF by the caller using the same byte the callee reads, so the callee never returns "device present" for an initial 0xFF status. The in-loop 0xFF case (device removed during the internal busy-wait) resolves through return PortUchar == 127; in both builds identically. No sequence of device states produces a different result code, IRB byte, or control-flow branch between unpatched and patched.

6. Exploit Primitive & Development Notes

None. The change is a relocation of an existing 0xFF status check from two callers into their shared callee, with identical observable behavior. No new bound, probe, or state check is introduced that was absent from the unpatched code path as a whole, and no check is weakened. There is no memory-safety, information-disclosure, or denial-of-service primitive attributable to this diff.

7. Debugger PoC Playbook

Not applicable — there is no behavioral difference to demonstrate. An analyst wishing to confirm equivalence can set a breakpoint on AtapiNoDeviceConnected in each build and on the two call sites, and observe that for any status byte the return value, the caller's chosen result code (5 for AtapiSendAtaIdentify, 6 for AtapiSendAtaCommand), and the byte written to *(Irb+3) match between builds. In the unpatched build the callee is never entered with an initial 0xFF status because the caller short-circuits first.

8. Changed Functions — Full Triage

  • AtapiNoDeviceConnected (sub_1C0001FC8) / AtapiNoDeviceConnected (sub_1C0001FD8) (Not security relevant): Added if (*a2 == 0xFF) return true; at entry. This duplicates the 0xFF filtering the two callers already performed in the unpatched build; observable behavior is unchanged.
  • AtapiSendAtaIdentify (sub_1C00020C0) / AtapiSendAtaIdentify (sub_1C00020E0) (Not security relevant): Removed the caller-level 0xFF pre-check (v7 == -1 || ... / cmp dl, 0FFh; jz) and now relies on the callee's return value. Same result code (5) and same *(Irb+3) write for a 0xFF status.
  • AtapiSendAtaCommand (sub_1C0002200) / AtapiSendAtaCommand (sub_1C0002210) (Not security relevant): Removed the caller-level 0xFF pre-check (cmp al, 0FFh; jz at 0x1C000226C) and relies on the patched callee. Same result code (6) and same *(Irb+3) write.
  • AtapiHandleMiniportCommand (sub_1C000272C) / AtapiHandleMiniportCommand (sub_1C0002740) (Not security relevant): Control-flow restructuring of the miniport command dispatch (codes 0x401-0x405) with a shared completion tail. The per-code handling — including the mov ebx, 1 set after the AtapiCompleteRequest call on code 0x404, which is present in both builds — is behaviorally equivalent.
  • AtapiProcessInterrupt (sub_1C0002AA0) / AtapiProcessInterrupt (sub_1C0002AB0) (Not security relevant): Bit-test codegen change (test-with-mask replaced by bt) and zero-extension/register-allocation churn. No logic change.
  • WaitOnBusyUntil (sub_1C00012C8) / WaitOnBusyUntil (sub_1C00012CC) (Not security relevant): Loop control-flow restructuring. AtapiDeviceErrorHistoryLog is still called only when the busy bit persists until the retry count is exhausted; net behavior unchanged.
  • Cosmetic & Register Allocation Changes: The remaining functions (AtapiInitializeDevice (sub_1C0001D64), AtapiSendAtapiCommand (sub_1C0002500), AtapiHwStartIo (sub_1C0001700), AtapiCompleteRequest (sub_1C0002DDC), AtapiWaitStatusAsync (sub_1C0003730), AtapiResetAtapiDevices (sub_1C00039C0), AtapiSetDmaTransferModeAsync (sub_1C0003C80), AtapiHwBuildIo (sub_1C0001690), AtapiSetBlockSizeAsync (sub_1C0003EC0), AtapiSetupGeometryAsync (sub_1C0003DF0), AtapiSetPioTransferModeAsync (sub_1C0003BD0), __GSHandlerCheckCommon (sub_1C0003FFC)) received compiler codegen differences: bit-test instruction selection (bt vs masked test), direct and-zero stores replacing xor+mov, and register reallocation. None affect security posture.

9. Unmatched Functions

There were no unmatched functions added or removed in this patch.

10. Confidence & Caveats

  • Confidence: High. The callee difference is a single added 0xFF entry check, visible in both the disassembly and the decompilation; the two callers drop matching 0xFF pre-checks that operated on the same status byte. Tracing every status value through the callee and its only two callers yields identical result codes and IRB side effects in both builds.
  • Assumptions: Based on the binary context this is the Microsoft Windows ATAPI miniport driver. The call graph was read directly from the two call sites of AtapiNoDeviceConnected.
  • Verification: AtapiNoDeviceConnected has exactly two callers (AtapiSendAtaIdentify, AtapiSendAtaCommand); both were inspected in both builds. No other function reads the callee's return value or relies on its 0xFF handling.