Repository navigation
Commit d25f80c
authored
fix(winscard): fix SCardControl on macOS and pcsc-lite (#749)
### Summary
`SCardControl` appears broken in two independent ways in the
`system_scard` backend on non-Windows platforms. On macOS the first is a
hard crash: the process segfaults inside PCSC.framework when an RDP
server sends `CM_IOCTL_GET_FEATURE_REQUEST` for a redirected smart card.
Reported from the field on RDM Mac (macOS 26.6.2, SafeNet eToken, NLA +
Kerberos smartcard logon); the session logs are analysed below.
**1. Calling convention mismatch on macOS (segfault)**
PCSC.framework still exports the legacy pcsc-lite 1.1.2 SCardControl:
```
C
int32_t SCardControl(SCARDHANDLE hCard,
const void *pbSendBuffer, uint32_t cbSendLength,
void *pbRecvBuffer, uint32_t *pcbRecvLength); // 5 parameters
int32_t SCardControl132(SCARDHANDLE hCard, uint32_t dwControlCode,
const void *pbSendBuffer, uint32_t cbSendLength,
void *pbRecvBuffer, uint32_t cbRecvLength,
uint32_t *lpBytesReturned); // 7 parameters
#if !defined(USE_SCARD_CONTROL_112)
#define SCardControl SCardControl132
#endif
```
C callers only reach the 7-parameter function through that `#define`. We
resolve symbols at runtime with dlsym, so we were getting the
5-parameter function and calling it through a 7-parameter signature.
Both symbols exist at distinct addresses, and the 48-byte `SCardControl`
thunk is the 1.1.2 shim:
```
cmp w2, #0x10 ; reject if the (shifted) cbSendLength > 16
b.ls +0x10
mov w0, #0x80100004 ; SCARD_E_INVALID_PARAMETER
ret
mov x2, x1 ; pbSendBuffer
ldr w5, [x6] ; cbRecvLength = *pcbRecvLength
mov w1, #0 ; dwControlCode = 0
b SCardControl132
```
Every argument shifted by one slot: the control code landed in
`pbSendBuffer`, and the input pointer landed in `cbSendLength`.
**Why only GET_FEATURE_REQUEST crashes**
This is the non-obvious part, and it explains why the bug stayed latent.
The cmp w2, #0x10 guard decides the outcome:
|Control request|Shifted `cbSendLength`|Result|
|---------------|-------------------------|------|
|With a payload|low 32 bits of a real pointer (huge)|fails the guard →
`SCARD_E_INVALID_PARAMETER`, graceful|
|Zero-length input|Rust's empty-slice pointer, 0x1|passes the guard →
dereferences `pbSendBuffer`|
In the second case the framework reads from 0x00313520 - the control
code reinterpreted as an address. On arm64 macOS __PAGEZERO spans
0x0–0x100000000, so that is guaranteed unmapped: **deterministic
SIGSEGV**. `CM_IOCTL_GET_FEATURE_REQUEST` is always zero-length, so it
is the one request that reliably kills the process. The no-output
`control()` path was unconditionally fatal as well, since it passed
`null` for `lpBytesReturned` and the shim does ldr w5, [x6] on it.
Verified empirically against the system framework: calling the legacy
symbol through the 7-parameter signature returns 0x80100004 with a real
send pointer, but 0x80100003 (i.e. it got past parameter validation and
reached handle validation) with 0x1.
Fixed by loading `SCardControl132` on macOS. This mirrors FreeRDP's own
PC/SC backend (winpr/libwinpr/smartcard/smartcard_pcsc.c). The
`SCardControl` fallback is deliberately not for older frameworks;
falling back to the legacy function would reintroduce the bug. It exists
because `PCSC_LITE_LIB_PATH` allows loading a genuine pcsc-lite build on
macOS, where `SCardControl` is the 7-parameter function and
`SCardControl132` does not exist.
**2. Control codes were never translated to the pcsc-lite encoding**
| |Encoding|CM_IOCTL_GET_FEATURE_REQUEST|
|-|---------|--------------------------------------|
|Windows|`CTL_CODE(FILE_DEVICE_SMARTCARD, code, METHOD_BUFFERED,
FILE_ANY_ACCESS)`|0x00313520|
|pcsc-lite|0x42000000 + code|0x42000D48|
The RDP server always sends Windows-encoded codes over rdpdr, and we
forwarded them verbatim. Even with the ABI fixed, every control request
would have been rejected by the reader driver, so both halves of this
patch are needed. Added `win_scard_ctl_code_to_pcsc_lite,` applied in
the `SystemScard` control methods on non-Windows targets only; Windows
passes codes through unchanged, and codes outside the smart card device
type are left untouched so callers already using pcsc-lite encoding keep
working.
**3. Related soundness fixes**
`input.as_ptr()` on an empty slice yields the dangling pointer 0x1. We
now pass `null`, which both APIs permit when `cbSendLength` is 0. This
is precisely the value that defeats the shim's guard, so it is also
defence in depth.
`control()` passed `null` for `lpBytesReturned`; pcsc-lite dereferences
it unconditionally, so it now receives a valid local.
**Evidence from the reported session**
The FreeRDP log ends mid-call: these are the final lines of the file,
with no `Control_Return`:
> smartcard_irp_device_control_decode_request: SCardControl (0x000900D4)
FileId: 1 CompletionId: 1
> smartcard_trace_control_call: Control_Call {
> smartcard_trace_control_call: dwControlCode: 0x00313520
cbInBufferSize: 0 fpvOutBufferIsNULL: 0 cbOutBufferSize: 256
> smartcard_trace_control_call: pvInBuffer: null
> smartcard_trace_control_call: }
`dwControlCode`: 0x00313520 is the untranslated Windows encoding, and
`cbInBufferSize`: 0 / `pvInBuffer`: `null` is exactly the zero-length
shape that reaches the faulting dereference. The sspi-rs log contains no
`SCardControl` entry and stops at the immediately preceding
`SCardTransmit` return, which is expected: the `fmt` layer uses the
default `FmtSpan::NONE`, so `#[instrument(ret)]` only emits on return.
No panic-hook output either, consistent with a segfault rather than a
Rust panic.
**Testing**
- Unit tests for the control code translation: `GET_FEATURE_REQUEST`,
several `IOCTL_SMARTCARD_*` codes, already-pcsc-lite-encoded values, and
non-smart-card device types.
- Unit tests pinning the empty-slice pointer behaviour that triggers the
crash, and that `control_input_ptr` converts it to `null`.
- Verified against the real PCSC.framework on macOS 26 (arm64) that
`initialize_pcsc_lite_api` now resolves `SCardControl` to
`SCardControl132` (0x238a0d480) instead of the legacy thunk
(0x238a0d450).
- `cargo clippy -p sspi-ffi --all-targets clean; cargo test -p sspi-ffi
--lib` passes (32 tests).
- Compile-checked the `Dword` = `c_ulong` configuration used on Linux,
since `Dword` is u32 only on macOS.1 parent f73e31c commit d25f80c
2 files changed
Lines changed: 169 additions & 19 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
124 | 145 | | |
125 | | - | |
126 | | - | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
127 | 149 | | |
128 | 150 | | |
129 | 151 | | |
| |||
147 | 169 | | |
148 | 170 | | |
149 | 171 | | |
150 | | - | |
151 | | - | |
152 | | - | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
153 | 175 | | |
154 | 176 | | |
155 | 177 | | |
156 | | - | |
157 | | - | |
158 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
159 | 181 | | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
160 | 188 | | |
161 | 189 | | |
162 | 190 | | |
| |||
165 | 193 | | |
166 | 194 | | |
167 | 195 | | |
168 | | - | |
| 196 | + | |
169 | 197 | | |
170 | 198 | | |
171 | 199 | | |
172 | 200 | | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
173 | 208 | | |
174 | 209 | | |
175 | 210 | | |
| |||
189 | 224 | | |
190 | 225 | | |
191 | 226 | | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
192 | 255 | | |
193 | 256 | | |
194 | 257 | | |
| |||
199 | 262 | | |
200 | 263 | | |
201 | 264 | | |
202 | | - | |
| 265 | + | |
203 | 266 | | |
204 | 267 | | |
205 | 268 | | |
| |||
214 | 277 | | |
215 | 278 | | |
216 | 279 | | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
30 | 58 | | |
31 | 59 | | |
32 | 60 | | |
| |||
217 | 245 | | |
218 | 246 | | |
219 | 247 | | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
220 | 253 | | |
221 | 254 | | |
222 | 255 | | |
223 | | - | |
| 256 | + | |
224 | 257 | | |
225 | 258 | | |
226 | | - | |
| 259 | + | |
227 | 260 | | |
228 | 261 | | |
229 | 262 | | |
230 | | - | |
231 | | - | |
| 263 | + | |
| 264 | + | |
232 | 265 | | |
233 | 266 | | |
234 | 267 | | |
235 | | - | |
| 268 | + | |
236 | 269 | | |
237 | 270 | | |
238 | 271 | | |
| |||
242 | 275 | | |
243 | 276 | | |
244 | 277 | | |
| 278 | + | |
| 279 | + | |
245 | 280 | | |
246 | 281 | | |
247 | 282 | | |
248 | 283 | | |
249 | 284 | | |
250 | 285 | | |
251 | | - | |
| 286 | + | |
252 | 287 | | |
253 | 288 | | |
254 | 289 | | |
255 | 290 | | |
256 | 291 | | |
257 | 292 | | |
258 | | - | |
259 | | - | |
| 293 | + | |
| 294 | + | |
260 | 295 | | |
261 | 296 | | |
262 | 297 | | |
| |||
443 | 478 | | |
444 | 479 | | |
445 | 480 | | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
0 commit comments