mirror of
https://git.zx2c4.com/wireguard-nt
synced 2024-09-22 06:11:35 +00:00
f394d011c3
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
675 lines
24 KiB
C
675 lines
24 KiB
C
/* SPDX-License-Identifier: GPL-2.0
|
|
*
|
|
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
|
*/
|
|
|
|
#include "device.h"
|
|
#include "ioctl.h"
|
|
#include "peer.h"
|
|
#include "queueing.h"
|
|
#include "socket.h"
|
|
#include "timers.h"
|
|
#include "logging.h"
|
|
#include <ntddk.h>
|
|
|
|
#define SIZE_OF_EMBEDDED(A, B) \
|
|
FIELD_OFFSET( \
|
|
struct { \
|
|
A _A; \
|
|
B _B; \
|
|
}, \
|
|
_B)
|
|
|
|
static_assert(
|
|
SIZE_OF_EMBEDDED(WG_IOCTL_INTERFACE, WG_IOCTL_PEER) == (sizeof(WG_IOCTL_INTERFACE)),
|
|
"Non-symmetric interface stacking");
|
|
static_assert(
|
|
SIZE_OF_EMBEDDED(WG_IOCTL_PEER, WG_IOCTL_ALLOWED_IP) == SIZE_OF_EMBEDDED(WG_IOCTL_PEER, WG_IOCTL_INTERFACE),
|
|
"Non-symmetric peer stacking");
|
|
static_assert(
|
|
SIZE_OF_EMBEDDED(WG_IOCTL_PEER, WG_IOCTL_ALLOWED_IP) == (sizeof(WG_IOCTL_PEER)),
|
|
"Non-symmetric peer stacking");
|
|
static_assert(SIZE_OF_EMBEDDED(WG_IOCTL_PEER, WG_IOCTL_PEER) == (sizeof(WG_IOCTL_PEER)), "Non-symmetric peer stacking");
|
|
static_assert(
|
|
SIZE_OF_EMBEDDED(WG_IOCTL_ALLOWED_IP, WG_IOCTL_ALLOWED_IP) == SIZE_OF_EMBEDDED(WG_IOCTL_ALLOWED_IP, WG_IOCTL_PEER),
|
|
"Non-symmetric allowed IP stacking");
|
|
static_assert(
|
|
SIZE_OF_EMBEDDED(WG_IOCTL_ALLOWED_IP, WG_IOCTL_PEER) == (sizeof(WG_IOCTL_ALLOWED_IP)),
|
|
"Non-symmetric allowed IP stacking");
|
|
static_assert(
|
|
SIZE_OF_EMBEDDED(WG_IOCTL_ALLOWED_IP, WG_IOCTL_ALLOWED_IP) == (sizeof(WG_IOCTL_ALLOWED_IP)),
|
|
"Non-symmetric allowed IP stacking");
|
|
|
|
#undef SIZE_OF_EMBEDDED
|
|
|
|
#pragma warning(disable : 28175) /* undocumented: the member of struct should not be accessed by a driver */
|
|
|
|
static DRIVER_DISPATCH *NdisDispatchDeviceControl, *NdisDispatchCreate, *NdisDispatchPnp;
|
|
/* The following binary blob security descriptor was generated via:
|
|
* PSECURITY_DESCRIPTOR Sd;
|
|
* ULONG SdLen;
|
|
* ConvertStringSecurityDescriptorToSecurityDescriptorA("O:SYD:P(A;;FA;;;SY)(A;;FA;;;BA)S:(ML;;NWNRNX;;;HI)",
|
|
* SDDL_REVISION_1, &Sd, &SdLen);
|
|
* for (ULONG i = 0; i < SdLen; ++i)
|
|
* printf("0x%02x%s%s", ((UCHAR *)Sd)[i], i == SdLen - 1 ? "" : ",", i == SdLen - 1 || i % 8 == 7 ? "\n": " ");
|
|
*/
|
|
static SECURITY_DESCRIPTOR *DispatchSecurityDescriptor = (SECURITY_DESCRIPTOR *)(__declspec(align(8)) UCHAR[]){
|
|
0x01, 0x00, 0x14, 0x90, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
|
|
0x00, 0x02, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x14, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x00, 0x02, 0x00, 0x34, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x14, 0x00, 0xff, 0x01, 0x1f, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x18, 0x00, 0xff, 0x01, 0x1f, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x20, 0x00, 0x00,
|
|
0x00, 0x20, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static BOOLEAN
|
|
HasAccess(_In_ ACCESS_MASK DesiredAccess, _In_ KPROCESSOR_MODE AccessMode, _Out_ NTSTATUS *Status)
|
|
{
|
|
SECURITY_SUBJECT_CONTEXT SubjectContext;
|
|
SeCaptureSubjectContext(&SubjectContext);
|
|
ACCESS_MASK GrantedAccess;
|
|
BOOLEAN HasAccess = SeAccessCheck(
|
|
DispatchSecurityDescriptor,
|
|
&SubjectContext,
|
|
FALSE,
|
|
DesiredAccess,
|
|
0,
|
|
NULL,
|
|
IoGetFileObjectGenericMapping(),
|
|
AccessMode,
|
|
&GrantedAccess,
|
|
Status);
|
|
SeReleaseSubjectContext(&SubjectContext);
|
|
return HasAccess;
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static VOID
|
|
Get(_In_ DEVICE_OBJECT *DeviceObject, _Inout_ IRP *Irp)
|
|
{
|
|
Irp->IoStatus.Information = 0;
|
|
if (!HasAccess(FILE_READ_DATA, Irp->RequestorMode, &Irp->IoStatus.Status))
|
|
return;
|
|
|
|
WG_IOCTL_INTERFACE *IoctlInterface = NULL;
|
|
if (Irp->MdlAddress)
|
|
{
|
|
IoctlInterface = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority | MdlMappingNoExecute);
|
|
if (!IoctlInterface)
|
|
{
|
|
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
return;
|
|
}
|
|
}
|
|
|
|
WG_DEVICE *Wg = DeviceObject->Reserved;
|
|
if (!Wg || ReadBooleanNoFence(&Wg->IsDeviceRemoving))
|
|
{
|
|
Irp->IoStatus.Status = NDIS_STATUS_ADAPTER_REMOVED;
|
|
return;
|
|
}
|
|
|
|
MuAcquirePushLockExclusive(&Wg->DeviceUpdateLock);
|
|
|
|
ULONG OutSize = IoctlInterface ? MmGetMdlByteCount(Irp->MdlAddress) : 0;
|
|
ULONG64 FinalSize = sizeof(WG_IOCTL_INTERFACE);
|
|
if (OutSize >= FinalSize)
|
|
{
|
|
IoctlInterface->Flags = 0;
|
|
IoctlInterface->PeersCount = 0;
|
|
if (Wg->IncomingPort != 0)
|
|
{
|
|
IoctlInterface->ListenPort = Wg->IncomingPort;
|
|
IoctlInterface->Flags |= WG_IOCTL_INTERFACE_HAS_LISTEN_PORT;
|
|
}
|
|
MuAcquirePushLockShared(&Wg->StaticIdentity.Lock);
|
|
if (Wg->StaticIdentity.HasIdentity)
|
|
{
|
|
RtlCopyMemory(IoctlInterface->PrivateKey, Wg->StaticIdentity.StaticPrivate, NOISE_PUBLIC_KEY_LEN);
|
|
RtlCopyMemory(IoctlInterface->PublicKey, Wg->StaticIdentity.StaticPublic, NOISE_PUBLIC_KEY_LEN);
|
|
IoctlInterface->Flags |= WG_IOCTL_INTERFACE_HAS_PUBLIC_KEY | WG_IOCTL_INTERFACE_HAS_PRIVATE_KEY;
|
|
}
|
|
MuReleasePushLockShared(&Wg->StaticIdentity.Lock);
|
|
}
|
|
|
|
WG_IOCTL_PEER *IoctlPeer = (WG_IOCTL_PEER *)((UCHAR *)IoctlInterface + sizeof(WG_IOCTL_INTERFACE));
|
|
WG_PEER *Peer;
|
|
LIST_FOR_EACH_ENTRY (Peer, &Wg->PeerList, WG_PEER, PeerList)
|
|
{
|
|
FinalSize += sizeof(WG_IOCTL_PEER);
|
|
if (OutSize >= FinalSize)
|
|
{
|
|
++IoctlInterface->PeersCount;
|
|
IoctlPeer->Flags = WG_IOCTL_PEER_HAS_PERSISTENT_KEEPALIVE;
|
|
IoctlPeer->ProtocolVersion = 1;
|
|
IoctlPeer->PersistentKeepalive = Peer->PersistentKeepaliveInterval;
|
|
IoctlPeer->RxBytes = Peer->RxBytes;
|
|
IoctlPeer->TxBytes = Peer->TxBytes;
|
|
IoctlPeer->LastHandshake = Peer->WalltimeLastHandshake.QuadPart;
|
|
IoctlPeer->AllowedIPsCount = 0;
|
|
MuAcquirePushLockShared(&Peer->Handshake.Lock);
|
|
RtlCopyMemory(IoctlPeer->PublicKey, Peer->Handshake.RemoteStatic, NOISE_PUBLIC_KEY_LEN);
|
|
IoctlPeer->Flags |= WG_IOCTL_PEER_HAS_PUBLIC_KEY;
|
|
if (!CryptoIsZero32(Peer->Handshake.PresharedKey))
|
|
{
|
|
RtlCopyMemory(IoctlPeer->PresharedKey, Peer->Handshake.PresharedKey, NOISE_SYMMETRIC_KEY_LEN);
|
|
IoctlPeer->Flags |= WG_IOCTL_PEER_HAS_PRESHARED_KEY;
|
|
}
|
|
MuReleasePushLockShared(&Peer->Handshake.Lock);
|
|
KIRQL Irql;
|
|
Irql = ExAcquireSpinLockShared(&Peer->EndpointLock);
|
|
if (Peer->Endpoint.Addr.si_family == AF_INET)
|
|
{
|
|
IoctlPeer->Endpoint.Ipv4 = Peer->Endpoint.Addr.Ipv4;
|
|
IoctlPeer->Flags |= WG_IOCTL_PEER_HAS_ENDPOINT;
|
|
}
|
|
else if (Peer->Endpoint.Addr.si_family == AF_INET6)
|
|
{
|
|
IoctlPeer->Endpoint.Ipv6 = Peer->Endpoint.Addr.Ipv6;
|
|
IoctlPeer->Flags |= WG_IOCTL_PEER_HAS_ENDPOINT;
|
|
}
|
|
ExReleaseSpinLockShared(&Peer->EndpointLock, Irql);
|
|
}
|
|
|
|
WG_IOCTL_ALLOWED_IP *IoctlAllowedIp = (WG_IOCTL_ALLOWED_IP *)((UCHAR *)IoctlPeer + sizeof(WG_IOCTL_PEER));
|
|
ALLOWEDIPS_NODE *AllowedIpsNode;
|
|
ULONG AllowedIpsLimit = MAXULONG;
|
|
LIST_FOR_EACH_ENTRY (AllowedIpsNode, &Peer->AllowedIpsList, ALLOWEDIPS_NODE, PeerList)
|
|
{
|
|
if (!(--AllowedIpsLimit))
|
|
break;
|
|
FinalSize += sizeof(WG_IOCTL_ALLOWED_IP);
|
|
if (OutSize >= FinalSize)
|
|
{
|
|
++IoctlPeer->AllowedIPsCount;
|
|
IoctlAllowedIp->AddressFamily =
|
|
AllowedIpsReadNode(AllowedIpsNode, (UINT8 *)&IoctlAllowedIp->Address, &IoctlAllowedIp->Cidr);
|
|
}
|
|
++IoctlAllowedIp;
|
|
}
|
|
IoctlPeer = (WG_IOCTL_PEER *)IoctlAllowedIp;
|
|
}
|
|
MuReleasePushLockExclusive(&Wg->DeviceUpdateLock);
|
|
|
|
Irp->IoStatus.Status = OutSize >= FinalSize ? STATUS_SUCCESS
|
|
: FinalSize <= MAXULONG ? STATUS_BUFFER_OVERFLOW
|
|
: STATUS_SECTION_TOO_BIG;
|
|
Irp->IoStatus.Information = (ULONG_PTR)FinalSize;
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
_Requires_lock_held_(Wg->DeviceUpdateLock)
|
|
_Must_inspect_result_
|
|
static NTSTATUS
|
|
SetListenPort(_Inout_ WG_DEVICE *Wg, _In_ USHORT ListenPort)
|
|
{
|
|
if (Wg->IncomingPort == ListenPort)
|
|
return STATUS_SUCCESS;
|
|
WG_PEER *Peer;
|
|
LIST_FOR_EACH_ENTRY (Peer, &Wg->PeerList, WG_PEER, PeerList)
|
|
SocketClearPeerEndpointSrc(Peer);
|
|
if (ReadBooleanNoFence(&Wg->IsUp))
|
|
return SocketInit(Wg, ListenPort);
|
|
Wg->IncomingPort = ListenPort;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
_Requires_lock_held_(Wg->DeviceUpdateLock)
|
|
_Must_inspect_result_
|
|
static NTSTATUS
|
|
SetPrivateKey(_Inout_ WG_DEVICE *Wg, _In_ CONST UCHAR PrivateKey[WG_KEY_LEN])
|
|
{
|
|
UINT8 PublicKey[NOISE_PUBLIC_KEY_LEN];
|
|
WG_PEER *Peer, *Temp;
|
|
if (CryptoEqualMemory32(Wg->StaticIdentity.StaticPrivate, PrivateKey))
|
|
return STATUS_SUCCESS;
|
|
|
|
/* We remove before setting, to prevent race, which means doing two 25519-genpub ops. */
|
|
if (Curve25519GeneratePublic(PublicKey, PrivateKey))
|
|
{
|
|
Peer = PubkeyHashtableLookup(Wg->PeerHashtable, PublicKey);
|
|
if (Peer)
|
|
{
|
|
PeerPut(Peer);
|
|
_Analysis_assume_same_lock_(Peer->Device->DeviceUpdateLock, Wg->DeviceUpdateLock);
|
|
PeerRemove(Peer);
|
|
}
|
|
}
|
|
|
|
MuAcquirePushLockExclusive(&Wg->StaticIdentity.Lock);
|
|
NoiseSetStaticIdentityPrivateKey(&Wg->StaticIdentity, PrivateKey);
|
|
LIST_FOR_EACH_ENTRY_SAFE (Peer, Temp, &Wg->PeerList, WG_PEER, PeerList)
|
|
{
|
|
_Analysis_assume_same_lock_(Peer->Device->DeviceUpdateLock, Wg->DeviceUpdateLock);
|
|
NoisePrecomputeStaticStatic(Peer);
|
|
NoiseExpireCurrentPeerKeypairs(Peer);
|
|
}
|
|
_Analysis_assume_same_lock_(Wg->CookieChecker.Device->DeviceUpdateLock, Wg->DeviceUpdateLock);
|
|
CookieCheckerPrecomputeDeviceKeys(&Wg->CookieChecker);
|
|
MuReleasePushLockExclusive(&Wg->StaticIdentity.Lock);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
_Requires_lock_held_(Wg->DeviceUpdateLock)
|
|
_Must_inspect_result_
|
|
static NTSTATUS
|
|
SetPeer(_Inout_ WG_DEVICE *Wg, _Inout_ CONST volatile WG_IOCTL_PEER **UnsafeIoctlPeerPtr, _Inout_ ULONG *RemainingSize)
|
|
{
|
|
if (*RemainingSize < sizeof(WG_IOCTL_PEER))
|
|
return STATUS_INVALID_PARAMETER;
|
|
*RemainingSize -= sizeof(WG_IOCTL_PEER);
|
|
|
|
CONST volatile WG_IOCTL_PEER *UnsafeIoctlPeer = *UnsafeIoctlPeerPtr;
|
|
WG_IOCTL_PEER IoctlPeer = *UnsafeIoctlPeer;
|
|
|
|
NTSTATUS Status = STATUS_INVALID_PARAMETER;
|
|
if (!(IoctlPeer.Flags & WG_IOCTL_PEER_HAS_PUBLIC_KEY))
|
|
goto cleanupStack;
|
|
Status = STATUS_NOT_IMPLEMENTED;
|
|
if ((IoctlPeer.Flags & WG_IOCTL_PEER_HAS_PROTOCOL_VERSION) && IoctlPeer.ProtocolVersion != 0 &&
|
|
IoctlPeer.ProtocolVersion > 1)
|
|
goto cleanupStack;
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
ULONG AllowedIPsSize;
|
|
if (!NT_SUCCESS(RtlULongMult(IoctlPeer.AllowedIPsCount, sizeof(WG_IOCTL_ALLOWED_IP), &AllowedIPsSize)))
|
|
goto cleanupStack;
|
|
if (*RemainingSize < AllowedIPsSize)
|
|
goto cleanupStack;
|
|
*RemainingSize -= AllowedIPsSize;
|
|
CONST WG_IOCTL_ALLOWED_IP *UnsafeIoctlAllowedIp =
|
|
(CONST WG_IOCTL_ALLOWED_IP *)((UCHAR *)UnsafeIoctlPeer + sizeof(WG_IOCTL_PEER));
|
|
*UnsafeIoctlPeerPtr = (CONST WG_IOCTL_PEER *)((UCHAR *)UnsafeIoctlAllowedIp + AllowedIPsSize);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
WG_PEER *Peer = PubkeyHashtableLookup(Wg->PeerHashtable, IoctlPeer.PublicKey);
|
|
if (!Peer)
|
|
{
|
|
/* Peer doesn't exist yet. Add a new one. */
|
|
if (IoctlPeer.Flags & (WG_IOCTL_PEER_REMOVE | WG_IOCTL_PEER_UPDATE_ONLY))
|
|
goto cleanupStack;
|
|
|
|
/* The peer is new, so there aren't allowed IPs to remove. */
|
|
IoctlPeer.Flags &= ~WG_IOCTL_PEER_REPLACE_ALLOWED_IPS;
|
|
|
|
MuAcquirePushLockShared(&Wg->StaticIdentity.Lock);
|
|
if (Wg->StaticIdentity.HasIdentity &&
|
|
RtlEqualMemory(IoctlPeer.PublicKey, Wg->StaticIdentity.StaticPublic, NOISE_PUBLIC_KEY_LEN))
|
|
{
|
|
/* We silently ignore peers that have the same public key as the device. The reason we do it silently is
|
|
* that we'd like for people to be able to reuse the same set of API calls across peers. */
|
|
MuReleasePushLockShared(&Wg->StaticIdentity.Lock);
|
|
goto cleanupStack;
|
|
}
|
|
MuReleasePushLockShared(&Wg->StaticIdentity.Lock);
|
|
|
|
Status = PeerCreate(
|
|
Wg,
|
|
IoctlPeer.PublicKey,
|
|
(IoctlPeer.Flags & WG_IOCTL_PEER_HAS_PRESHARED_KEY) ? IoctlPeer.PresharedKey : NULL,
|
|
&Peer);
|
|
if (!NT_SUCCESS(Status))
|
|
goto cleanupStack;
|
|
/* Take additional reference, as though we've just been looked up. */
|
|
PeerGet(Peer);
|
|
}
|
|
|
|
if (IoctlPeer.Flags & WG_IOCTL_PEER_REMOVE)
|
|
{
|
|
_Analysis_assume_same_lock_(Peer->Device->DeviceUpdateLock, Wg->DeviceUpdateLock);
|
|
PeerRemove(Peer);
|
|
Status = STATUS_SUCCESS;
|
|
goto cleanupPeer;
|
|
}
|
|
|
|
if (IoctlPeer.Flags & WG_IOCTL_PEER_HAS_PRESHARED_KEY)
|
|
{
|
|
MuAcquirePushLockExclusive(&Peer->Handshake.Lock);
|
|
RtlCopyMemory(&Peer->Handshake.PresharedKey, IoctlPeer.PresharedKey, NOISE_SYMMETRIC_KEY_LEN);
|
|
MuReleasePushLockExclusive(&Peer->Handshake.Lock);
|
|
}
|
|
|
|
if (IoctlPeer.Flags & WG_IOCTL_PEER_HAS_ENDPOINT)
|
|
{
|
|
SIZE_T Size;
|
|
if ((Size = sizeof(SOCKADDR_IN), IoctlPeer.Endpoint.si_family == AF_INET) ||
|
|
(Size = sizeof(SOCKADDR_IN6), IoctlPeer.Endpoint.si_family == AF_INET6))
|
|
{
|
|
ENDPOINT Endpoint = { { { 0 } } };
|
|
RtlCopyMemory(&Endpoint.Addr, &IoctlPeer.Endpoint, Size);
|
|
SocketSetPeerEndpoint(Peer, &Endpoint);
|
|
}
|
|
}
|
|
|
|
if (IoctlPeer.Flags & WG_IOCTL_PEER_REPLACE_ALLOWED_IPS)
|
|
AllowedIpsRemoveByPeer(&Wg->PeerAllowedIps, Peer, &Wg->DeviceUpdateLock);
|
|
|
|
for (ULONG i = 0; i < IoctlPeer.AllowedIPsCount; ++i)
|
|
{
|
|
WG_IOCTL_ALLOWED_IP IoctlAllowedIp = UnsafeIoctlAllowedIp[i];
|
|
if (IoctlAllowedIp.AddressFamily == AF_INET && IoctlAllowedIp.Cidr <= 32)
|
|
Status = AllowedIpsInsertV4(
|
|
&Peer->Device->PeerAllowedIps,
|
|
&IoctlAllowedIp.Address.V4,
|
|
IoctlAllowedIp.Cidr,
|
|
Peer,
|
|
&Wg->DeviceUpdateLock);
|
|
else if (IoctlAllowedIp.AddressFamily == AF_INET6 && IoctlAllowedIp.Cidr <= 128)
|
|
Status = AllowedIpsInsertV6(
|
|
&Peer->Device->PeerAllowedIps,
|
|
&IoctlAllowedIp.Address.V6,
|
|
IoctlAllowedIp.Cidr,
|
|
Peer,
|
|
&Wg->DeviceUpdateLock);
|
|
else
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
if (!NT_SUCCESS(Status))
|
|
goto cleanupPeer;
|
|
}
|
|
|
|
BOOLEAN IsUp = ReadBooleanNoFence(&Wg->IsUp);
|
|
if (IoctlPeer.Flags & WG_IOCTL_PEER_HAS_PERSISTENT_KEEPALIVE)
|
|
{
|
|
CONST BOOLEAN SendKeepalive = !Peer->PersistentKeepaliveInterval && IoctlPeer.PersistentKeepalive && IsUp;
|
|
Peer->PersistentKeepaliveInterval = IoctlPeer.PersistentKeepalive;
|
|
if (SendKeepalive)
|
|
PacketSendKeepalive(Peer);
|
|
}
|
|
|
|
if (IsUp)
|
|
PacketSendStagedPackets(Peer);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
cleanupPeer:
|
|
PeerPut(Peer);
|
|
cleanupStack:
|
|
RtlSecureZeroMemory(&IoctlPeer, sizeof(IoctlPeer));
|
|
return Status;
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static NTSTATUS
|
|
SetInterface(
|
|
_Inout_ WG_DEVICE *Wg,
|
|
_In_ CONST volatile WG_IOCTL_INTERFACE *UnsafeIoctlInterface,
|
|
_Inout_ ULONG *RemainingSize)
|
|
{
|
|
if (*RemainingSize < sizeof(WG_IOCTL_INTERFACE))
|
|
return STATUS_INVALID_PARAMETER;
|
|
*RemainingSize = *RemainingSize - sizeof(WG_IOCTL_INTERFACE);
|
|
|
|
WG_IOCTL_INTERFACE IoctlInterface = *UnsafeIoctlInterface;
|
|
|
|
MuAcquirePushLockExclusive(&Wg->DeviceUpdateLock);
|
|
|
|
NTSTATUS Status;
|
|
if (IoctlInterface.Flags & WG_IOCTL_INTERFACE_HAS_LISTEN_PORT)
|
|
{
|
|
Status = SetListenPort(Wg, IoctlInterface.ListenPort);
|
|
if (!NT_SUCCESS(Status))
|
|
goto cleanupLock;
|
|
}
|
|
|
|
if (IoctlInterface.Flags & WG_IOCTL_INTERFACE_REPLACE_PEERS)
|
|
PeerRemoveAll(Wg);
|
|
|
|
if (IoctlInterface.Flags & WG_IOCTL_INTERFACE_HAS_PRIVATE_KEY)
|
|
{
|
|
Status = SetPrivateKey(Wg, IoctlInterface.PrivateKey);
|
|
if (!NT_SUCCESS(Status))
|
|
goto cleanupLock;
|
|
}
|
|
|
|
CONST volatile WG_IOCTL_PEER *UnsafeIoctlPeer =
|
|
(CONST volatile WG_IOCTL_PEER *)((UCHAR *)UnsafeIoctlInterface + sizeof(WG_IOCTL_INTERFACE));
|
|
for (ULONG i = 0; i < IoctlInterface.PeersCount; ++i)
|
|
{
|
|
Status = SetPeer(Wg, &UnsafeIoctlPeer, RemainingSize);
|
|
if (!NT_SUCCESS(Status))
|
|
goto cleanupLock;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
cleanupLock:
|
|
MuReleasePushLockExclusive(&Wg->DeviceUpdateLock);
|
|
RtlSecureZeroMemory(&IoctlInterface, sizeof(IoctlInterface));
|
|
return Status;
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static VOID
|
|
Set(_In_ DEVICE_OBJECT *DeviceObject, _Inout_ IRP *Irp)
|
|
{
|
|
Irp->IoStatus.Information = 0;
|
|
if (!HasAccess(FILE_WRITE_DATA, Irp->RequestorMode, &Irp->IoStatus.Status))
|
|
return;
|
|
|
|
if (!Irp->MdlAddress)
|
|
{
|
|
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
|
return;
|
|
}
|
|
CONST volatile WG_IOCTL_INTERFACE *UnsafeIoctlInterface =
|
|
MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority | MdlMappingNoExecute | MdlMappingNoWrite);
|
|
if (!UnsafeIoctlInterface)
|
|
{
|
|
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
return;
|
|
}
|
|
|
|
WG_DEVICE *Wg = DeviceObject->Reserved;
|
|
if (!Wg || ReadBooleanNoFence(&Wg->IsDeviceRemoving))
|
|
{
|
|
Irp->IoStatus.Status = NDIS_STATUS_ADAPTER_REMOVED;
|
|
return;
|
|
}
|
|
|
|
ULONG InSize = MmGetMdlByteCount(Irp->MdlAddress);
|
|
ULONG RemainingSize = InSize;
|
|
Irp->IoStatus.Status = SetInterface(Wg, UnsafeIoctlInterface, &RemainingSize);
|
|
Irp->IoStatus.Information = InSize - RemainingSize;
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
_Requires_lock_held_(&Wg->DeviceUpdateLock)
|
|
static NTSTATUS
|
|
Up(_Inout_ WG_DEVICE *Wg)
|
|
{
|
|
if (ReadBooleanNoFence(&Wg->IsUp))
|
|
return STATUS_ALREADY_COMPLETE;
|
|
NT_ASSERT(!Wg->SocketOwnerProcess);
|
|
Wg->SocketOwnerProcess = PsGetCurrentProcess();
|
|
if (Wg->SocketOwnerProcess)
|
|
ObReferenceObject(Wg->SocketOwnerProcess);
|
|
NTSTATUS Status = SocketInit(Wg, Wg->IncomingPort);
|
|
if (!NT_SUCCESS(Status))
|
|
return Status;
|
|
WriteBooleanNoFence(&Wg->IsUp, TRUE);
|
|
DeviceStart(Wg);
|
|
DeviceIndicateConnectionStatus(Wg->MiniportAdapterHandle, MediaConnectStateConnected);
|
|
LogInfo(Wg, "Interface up");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
_Requires_lock_held_(&Wg->DeviceUpdateLock)
|
|
static NTSTATUS
|
|
Down(_Inout_ WG_DEVICE *Wg)
|
|
{
|
|
if (!ReadBooleanNoFence(&Wg->IsUp))
|
|
return STATUS_ALREADY_COMPLETE;
|
|
WriteBooleanNoFence(&Wg->IsUp, FALSE);
|
|
RcuSynchronize();
|
|
DeviceIndicateConnectionStatus(Wg->MiniportAdapterHandle, MediaConnectStateDisconnected);
|
|
DeviceStop(Wg);
|
|
SocketReinit(Wg, NULL, NULL, 0);
|
|
if (Wg->SocketOwnerProcess)
|
|
{
|
|
ObDereferenceObject(Wg->SocketOwnerProcess);
|
|
Wg->SocketOwnerProcess = NULL;
|
|
}
|
|
LogInfo(Wg, "Interface down");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static VOID
|
|
AdapterState(_In_ DEVICE_OBJECT *DeviceObject, _Inout_ IRP *Irp)
|
|
{
|
|
Irp->IoStatus.Information = 0;
|
|
if (!HasAccess(FILE_WRITE_DATA, Irp->RequestorMode, &Irp->IoStatus.Status))
|
|
return;
|
|
IO_STACK_LOCATION *Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
if (Stack->Parameters.DeviceIoControl.InputBufferLength != sizeof(WG_IOCTL_ADAPTER_STATE))
|
|
{
|
|
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
|
return;
|
|
}
|
|
WG_IOCTL_ADAPTER_STATE Op;
|
|
RtlCopyMemory(&Op, Irp->AssociatedIrp.SystemBuffer, sizeof(Op));
|
|
|
|
WG_DEVICE *Wg = DeviceObject->Reserved;
|
|
if (!Wg || ReadBooleanNoFence(&Wg->IsDeviceRemoving))
|
|
{
|
|
Irp->IoStatus.Status = NDIS_STATUS_ADAPTER_REMOVED;
|
|
return;
|
|
}
|
|
MuAcquirePushLockExclusive(&Wg->DeviceUpdateLock);
|
|
switch (Op)
|
|
{
|
|
case WG_IOCTL_ADAPTER_STATE_QUERY:
|
|
if (Stack->Parameters.DeviceIoControl.OutputBufferLength == sizeof(Op))
|
|
{
|
|
Op = ReadBooleanNoFence(&Wg->IsUp) ? WG_IOCTL_ADAPTER_STATE_UP : WG_IOCTL_ADAPTER_STATE_DOWN;
|
|
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, &Op, sizeof(Op));
|
|
Irp->IoStatus.Information = sizeof(Op);
|
|
}
|
|
else
|
|
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
case WG_IOCTL_ADAPTER_STATE_DOWN:
|
|
Irp->IoStatus.Status = Down(Wg);
|
|
break;
|
|
case WG_IOCTL_ADAPTER_STATE_UP:
|
|
Irp->IoStatus.Status = Up(Wg);
|
|
break;
|
|
default:
|
|
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
MuReleasePushLockExclusive(&Wg->DeviceUpdateLock);
|
|
}
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static VOID
|
|
ReadLogLine(_In_ DEVICE_OBJECT *DeviceObject, _Inout_ IRP *Irp)
|
|
{
|
|
Irp->IoStatus.Information = 0;
|
|
if (!HasAccess(FILE_READ_DATA, Irp->RequestorMode, &Irp->IoStatus.Status))
|
|
return;
|
|
|
|
WG_DEVICE *Wg = DeviceObject->Reserved;
|
|
if (!Wg || ReadBooleanNoFence(&Wg->IsDeviceRemoving))
|
|
{
|
|
Irp->IoStatus.Status = NDIS_STATUS_ADAPTER_REMOVED;
|
|
return;
|
|
}
|
|
|
|
IO_STACK_LOCATION *Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(WG_IOCTL_LOG_ENTRY))
|
|
{
|
|
Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
|
|
return;
|
|
}
|
|
Irp->IoStatus.Status = LogRingRead(&Wg->Log, Irp->AssociatedIrp.SystemBuffer, &Wg->IsDeviceRemoving);
|
|
if (NT_SUCCESS(Irp->IoStatus.Status))
|
|
Irp->IoStatus.Information = sizeof(WG_IOCTL_LOG_ENTRY);
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_DEVICE_CONTROL)
|
|
static DRIVER_DISPATCH_PAGED DispatchDeviceControl;
|
|
_Use_decl_annotations_
|
|
static NTSTATUS
|
|
DispatchDeviceControl(DEVICE_OBJECT *DeviceObject, IRP *Irp)
|
|
{
|
|
IO_STACK_LOCATION *Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
switch (Stack->Parameters.DeviceIoControl.IoControlCode)
|
|
{
|
|
case WG_IOCTL_GET:
|
|
Get(DeviceObject, Irp);
|
|
break;
|
|
case WG_IOCTL_SET:
|
|
Set(DeviceObject, Irp);
|
|
break;
|
|
case WG_IOCTL_SET_ADAPTER_STATE:
|
|
AdapterState(DeviceObject, Irp);
|
|
break;
|
|
case WG_IOCTL_READ_LOG_LINE:
|
|
ReadLogLine(DeviceObject, Irp);
|
|
break;
|
|
default:
|
|
return NdisDispatchDeviceControl(DeviceObject, Irp);
|
|
}
|
|
NTSTATUS Status = Irp->IoStatus.Status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
return Status;
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_CREATE)
|
|
static DRIVER_DISPATCH_PAGED DispatchCreate;
|
|
_Use_decl_annotations_
|
|
static NTSTATUS
|
|
DispatchCreate(DEVICE_OBJECT *DeviceObject, IRP *Irp)
|
|
{
|
|
WG_DEVICE *Wg = DeviceObject->Reserved;
|
|
if (Wg && ReadBooleanNoFence(&Wg->IsDeviceRemoving))
|
|
{
|
|
Irp->IoStatus.Status = NDIS_STATUS_ADAPTER_REMOVED;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
return NDIS_STATUS_ADAPTER_REMOVED;
|
|
}
|
|
return NdisDispatchCreate(DeviceObject, Irp);
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_PNP)
|
|
static DRIVER_DISPATCH_PAGED DispatchPnp;
|
|
_Use_decl_annotations_
|
|
static NTSTATUS
|
|
DispatchPnp(DEVICE_OBJECT *DeviceObject, IRP *Irp)
|
|
{
|
|
IO_STACK_LOCATION *Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
if (Stack->MinorFunction != IRP_MN_QUERY_REMOVE_DEVICE && Stack->MinorFunction != IRP_MN_SURPRISE_REMOVAL)
|
|
goto ndisDispatch;
|
|
|
|
WG_DEVICE *Wg = DeviceObject->Reserved;
|
|
if (!Wg)
|
|
goto ndisDispatch;
|
|
WriteBooleanNoFence(&Wg->IsDeviceRemoving, TRUE);
|
|
KeSetEvent(&Wg->Log.NewEntry, IO_NO_INCREMENT, FALSE);
|
|
|
|
ndisDispatch:
|
|
return NdisDispatchPnp(DeviceObject, Irp);
|
|
}
|
|
|
|
_Use_decl_annotations_
|
|
VOID
|
|
IoctlHalt(WG_DEVICE *Wg)
|
|
{
|
|
WritePointerNoFence(&Wg->FunctionalDeviceObject->Reserved, NULL);
|
|
KeSetEvent(&Wg->DeviceRemoved, IO_NETWORK_INCREMENT, FALSE);
|
|
}
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
# pragma alloc_text(INIT, IoctlDriverEntry)
|
|
#endif
|
|
_Use_decl_annotations_
|
|
VOID
|
|
IoctlDriverEntry(DRIVER_OBJECT *DriverObject)
|
|
{
|
|
NdisDispatchDeviceControl = DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL];
|
|
NdisDispatchCreate = DriverObject->MajorFunction[IRP_MJ_CREATE];
|
|
NdisDispatchPnp = DriverObject->MajorFunction[IRP_MJ_PNP];
|
|
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDeviceControl;
|
|
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
|
|
DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;
|
|
}
|