When creating an IPsec SA using the WFP API, IpsecSaContextSetSpi0()
doesn't seem to accept an odd SPI value. Why is this the case? RFC 4303 Section 2.1 and RFC 4302 Section 2.4 talk about valid SPI values for an IPsec SA. It makes no mention of odd values being invalid, and states that the only IANA-reserved values are 1-255, with 0 being reserved for implementation-specific use.
If we dump the assembly of C:\Windows\System32\FWPUCLNT.DLL
, we can see the following instructions:
$ gdb FWPUCLNT.DLL
(gdb) disassemble IPsecSaContextSetSpi0
Dump of assembler code for function fwpuclnt!IPsecSaContextSetSpi0:
0x0000000180012140 <+0>: mov %r9d,0x20(%rsp)
0x0000000180012145 <+5>: push %rbx
0x0000000180012146 <+6>: sub $0x40,%rsp
0x000000018001214a <+10>: xor %ebx,%ebx
0x000000018001214c <+12>: test %r9d,%r9d
0x000000018001214f <+15>: je 0x1800121c2 <fwpuclnt!IPsecSaContextSetSpi0+130>
0x0000000180012151 <+17>: test $0x1,%r9b
0x0000000180012155 <+21>: jne 0x1800121c2 <fwpuclnt!IPsecSaContextSetSpi0+130>
[...]
0x00000001800121c2 <+130>: mov $0x80320035,%eax
0x00000001800121c7 <+135>: add $0x40,%rsp
0x00000001800121cb <+139>: pop %rbx
0x00000001800121cc <+140>: retq
At offset 17, we see a bitwise AND that checks if the least significant bit of a value (likely the SPI) is non-zero. If it's non-zero, we jump to offset 130 and return the value 0x80320035
(which is FWP_E_INVALID_PARAMETER
). We can also see the same through a decompiler such as Hex-Rays:
DWORD __stdcall IPsecSaContextSetSpi0(
HANDLE engineHandle,
UINT64 id,
const IPSEC_GETSPI1 *getSpi,
IPSEC_SA_SPI inboundSpi)
{
__int64 v4; // rbx
__int64 v5; // r8
const char *v6; // rdx
unsigned int Pointer; // eax
IPSEC_SA_SPI v9; // [rsp+30h] [rbp-18h] BYREF
v4 = 0LL;
v9 = inboundSpi;
if ( inboundSpi && (inboundSpi & 1) == 0 )
{
if ( engineHandle )
{
Pointer = (unsigned int)sub_18001F1AC(
*(_QWORD *)engineHandle,
*((_QWORD *)engineHandle + 1),
id,
(__int64)getSpi,
(__int64)&v9).Pointer;
if ( !Pointer )
return sub_180002960(v4);
v5 = Pointer;
v6 = "FwppProxyBfeIPsecSaContextGetOrSetSpi";
}
else
{
v5 = 2150760476LL;
v6 = "IPsecSaContextSetSpi0";
}
v4 = sub_180006520((__int64)engineHandle, v6, v5);
return sub_180002960(v4);
}
if ( off_18007A000 != &off_18007A000
&& *((_BYTE *)off_18007A000 + 25) >= 2u
&& (*((_DWORD *)off_18007A000 + 7) & 0x100) != 0 )
{
sub_18001AE58(*((_QWORD *)off_18007A000 + 2), id, (__int64)getSpi, inboundSpi, id);
}
return -2144206795; // 0x80320035 aka FWP_E_INVALID_PARAMETER
}
Now the documentation on IPsecSaContextSetSpi0()
makes no mention of odd SPI values being invalid, and it doesn't talk about FWP_E_INVALID_PARAMETER
being a likely return value:
IPsecSaContextSetSpi0 function (fwpmu.h)
The documentation on WFP error codes also doesn't help:
WFP Error Codes
This goes against the RFCs as linked earlier, and it seriously breaks interoperability with other IPsec implementations such as Linux's XFRM. Is it just an accidental bug or is it an odd, but intentional choice that hasn't been documented anywhere?
IKEv2 implementations such as strongSwan have encountered problems with this when interfacing with WFP. This issue was encountered many years ago on Windows Server 2012: Issue #2750: setting WFP SA SPI failed: 0x80320035
And it is still being encountered in recent times: "setting WFP SA SPI failed" and "unable to install IPsec policies (SPD) in kernel" on Windows #2567