/*++ Copyright (c) 1997 Microsoft Corporation Module Name: entry.c Abstract: This module contains the entry-code for the IP Network Address Translator. Author: Abolade Gbadegesin (t-abolag) 11-July-1997 Revision History: William Ingle (billi) 12-May-2001 NULL security descriptor check --*/ #include "precomp.h" #pragma hdrstop // // GLOBAL DATA DEFINITIONS // COMPONENT_REFERENCE ComponentReference; // // Win32 device-name // WCHAR ExternalName[] = L"\\DosDevices\\IPNAT"; // // Device- and file-object for the IP driver // extern PDEVICE_OBJECT IpDeviceObject = NULL; extern PFILE_OBJECT IpFileObject = NULL; // // Device-object for the NAT driver // extern PDEVICE_OBJECT NatDeviceObject = NULL; // // Registry parameters key name // WCHAR ParametersName[] = L"Parameters"; // // Name of value holding reserved ports // WCHAR ReservedPortsName[] = L"ReservedPorts"; // // Start and end of reserved-port range // USHORT ReservedPortsLowerRange = DEFAULT_START_PORT; USHORT ReservedPortsUpperRange = DEFAULT_END_PORT; // // Device- and file-object for the TCP driver // extern PDEVICE_OBJECT TcpDeviceObject = NULL; extern PFILE_OBJECT TcpFileObject = NULL; extern HANDLE TcpDeviceHandle = NULL; // // Registry path for the driver's parameters // const WCHAR IpNatParametersPath[] = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services" L"\\IpNat\\Parameters"; // // Timeout interval for TCP session mappings // ULONG TcpTimeoutSeconds = DEFAULT_TCP_TIMEOUT; // // Bitmap of enabled tracing message classes // ULONG TraceClassesEnabled = 0; // // Registry trace-class value name // WCHAR TraceClassesEnabledName[] = L"TraceClassesEnabled"; // // Timeout interval for UDP and other message-oriented session mappings // ULONG UdpTimeoutSeconds = DEFAULT_UDP_TIMEOUT; #if NAT_WMI // // Copy of our registry path for WMI use. // UNICODE_STRING NatRegistryPath; #endif // // Name of value for allowing inbound non-unicast // WCHAR AllowInboundNonUnicastTrafficName[] = L"AllowInboundNonUnicastTraffic"; // // If true, non-unicast traffic will not be dropped // when recevied on a firewalled interface. // BOOLEAN AllowInboundNonUnicastTraffic = FALSE; // // FUNCTION PROTOTYPES (alphabetically) // NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ); NTSTATUS NatAdjustSecurityDescriptor( VOID ); VOID NatCleanupDriver( VOID ); VOID NatCreateExternalNaming( IN PUNICODE_STRING DeviceString ); VOID NatDeleteExternalNaming( VOID ); NTSTATUS NatInitializeDriver( VOID ); NTSTATUS NatSetFirewallHook( BOOLEAN Install ); VOID NatUnloadDriver( IN PDRIVER_OBJECT DriverObject ); #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT, DriverEntry) #pragma alloc_text(INIT, NatAdjustSecurityDescriptor) #pragma alloc_text(PAGE, NatCreateExternalNaming) #pragma alloc_text(PAGE, NatDeleteExternalNaming) #endif NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) /*++ Routine Description: Performs driver-initialization for NAT. Arguments: Return Value: STATUS_SUCCESS if initialization succeeded, error code otherwise. --*/ { WCHAR DeviceName[] = DD_IP_NAT_DEVICE_NAME; UNICODE_STRING DeviceString; LONG i; OBJECT_ATTRIBUTES ObjectAttributes; HANDLE ParametersKey; HANDLE ServiceKey; NTSTATUS status; UNICODE_STRING String; PAGED_CODE(); CALLTRACE(("DriverEntry\n")); #if DBG // // Open the registry key // InitializeObjectAttributes( &ObjectAttributes, RegistryPath, OBJ_CASE_INSENSITIVE, NULL, NULL ); status = ZwOpenKey(&ServiceKey, KEY_READ, &ObjectAttributes); if (NT_SUCCESS(status)) { RtlInitUnicodeString(&String, ParametersName); InitializeObjectAttributes( &ObjectAttributes, &String, OBJ_CASE_INSENSITIVE, ServiceKey, NULL ); status = ZwOpenKey(&ParametersKey, KEY_READ, &ObjectAttributes); ZwClose(ServiceKey); if (NT_SUCCESS(status)) { UCHAR Buffer[32]; ULONG BytesRead; PKEY_VALUE_PARTIAL_INFORMATION Value; RtlInitUnicodeString(&String, TraceClassesEnabledName); status = ZwQueryValueKey( ParametersKey, &String, KeyValuePartialInformation, (PKEY_VALUE_PARTIAL_INFORMATION)Buffer, sizeof(Buffer), &BytesRead ); ZwClose(ParametersKey); if (NT_SUCCESS(status) && ((PKEY_VALUE_PARTIAL_INFORMATION)Buffer)->Type == REG_DWORD) { TraceClassesEnabled = *(PULONG)((PKEY_VALUE_PARTIAL_INFORMATION)Buffer)->Data; } } } #endif #if NAT_WMI // // Record our registry path for WMI use // NatRegistryPath.Length = 0; NatRegistryPath.MaximumLength = RegistryPath->MaximumLength + sizeof( UNICODE_NULL ); NatRegistryPath.Buffer = ExAllocatePoolWithTag( PagedPool, NatRegistryPath.MaximumLength, NAT_TAG_WMI ); if( NatRegistryPath.Buffer ) { RtlCopyUnicodeString( &NatRegistryPath, RegistryPath ); } else { ERROR(("NAT: Unable to allocate string for RegistryPath\n")); return STATUS_NO_MEMORY; } #endif // // Create the device's object. // RtlInitUnicodeString(&DeviceString, DeviceName); status = IoCreateDevice( DriverObject, 0, &DeviceString, FILE_DEVICE_NETWORK, FILE_DEVICE_SECURE_OPEN, FALSE, &NatDeviceObject ); if (!NT_SUCCESS(status)) { ERROR(("IoCreateDevice failed (0x%08X)\n", status)); return status; } // // Adjust the security descriptor on the device object. // status = NatAdjustSecurityDescriptor(); if (!NT_SUCCESS(status)) { ERROR(("NatAdjustSecurityDescriptor failed (0x%08x)\n", status)); return status; } // // Initialize file-object tracking items // KeInitializeSpinLock(&NatFileObjectLock); NatOwnerProcessId = NULL; NatFileObjectCount = 0; // // Setup the driver object // DriverObject->DriverUnload = NatUnloadDriver; DriverObject->FastIoDispatch = &NatFastIoDispatch; DriverObject->DriverStartIo = NULL; for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { DriverObject->MajorFunction[i] = NatDispatch; } // // Create a Win32-accessible device object // NatCreateExternalNaming(&DeviceString); // // Initialize the driver's structures // status = NatInitializeDriver(); return status; } // DriverEntry NTSTATUS NatAdjustSecurityDescriptor( VOID ) /*++ Routine Description: Modifies the security descriptor on the NAT's device object so that only SYSTEM has any permissions. Arguments: none. Return Value: NTSTATUS - success/error code. --*/ { PACE_HEADER AceHeader; PSID AceSid; PACL Dacl; BOOLEAN DaclDefaulted; BOOLEAN DaclPresent; DWORD i; BOOLEAN MemoryAllocated; PSECURITY_DESCRIPTOR NatSD = NULL; PACL NewDacl = NULL; SECURITY_DESCRIPTOR NewSD; SECURITY_INFORMATION SecurityInformation; ULONG Size; NTSTATUS status; do { // // Get our original security descriptor // status = ObGetObjectSecurity( NatDeviceObject, &NatSD, &MemoryAllocated ); // ObGetObjectSecurity can return a NULL security descriptor // even with NT_SUCCESS status code if (!NT_SUCCESS(status) || (NULL==NatSD)) { break; } // // Obtain the Dacl from the security descriptor // status = RtlGetDaclSecurityDescriptor( NatSD, &DaclPresent, &Dacl, &DaclDefaulted ); if (!NT_SUCCESS(status)) { break; } ASSERT(FALSE != DaclPresent); // // Make a copy of the Dacl so that we can modify it. // NewDacl = ExAllocatePoolWithTag( PagedPool, Dacl->AclSize, NAT_TAG_SD ); if (NULL == NewDacl) { status = STATUS_NO_MEMORY; break; } RtlCopyMemory(NewDacl, Dacl, Dacl->AclSize); // // Loop through the DACL, removing any access allowed // entries that aren't for SYSTEM // for (i = 0; i < NewDacl->AceCount; i++) { status = RtlGetAce(NewDacl, i, &AceHeader); if (NT_SUCCESS(status)) { if (ACCESS_ALLOWED_ACE_TYPE == AceHeader->AceType) { AceSid = (PSID) &((ACCESS_ALLOWED_ACE*)AceHeader)->SidStart; if (!RtlEqualSid(AceSid, SeExports->SeLocalSystemSid)) { status = RtlDeleteAce(NewDacl, i); if (NT_SUCCESS(status)) { i -= 1; } } } } } ASSERT(NewDacl->AceCount > 0); // // Create a new security descriptor to hold the new Dacl. // status = RtlCreateSecurityDescriptor( &NewSD, SECURITY_DESCRIPTOR_REVISION ); if (!NT_SUCCESS(status)) { break; } // // Place the new Dacl into the new SD // status = RtlSetDaclSecurityDescriptor( &NewSD, TRUE, NewDacl, FALSE ); if (!NT_SUCCESS(status)) { break; } // // Set the new SD into our device object. Only the Dacl from the // SD will be set. // SecurityInformation = DACL_SECURITY_INFORMATION; status = ObSetSecurityObjectByPointer( NatDeviceObject, SecurityInformation, &NewSD ); } while (FALSE); if (NULL != NatSD) { ObReleaseObjectSecurity(NatSD, MemoryAllocated); } if (NULL != NewDacl) { ExFreePool(NewDacl); } return status; } // NatAdjustSecurityDescriptor VOID NatCleanupDriver( VOID ) /*++ Routine Description: This routine is invoked when the last reference to the NAT driver is released. Arguments: none. Return Value: none. --*/ { CALLTRACE(("NatCleanupDriver\n")); } // NatCleanupDriver VOID NatCreateExternalNaming( IN PUNICODE_STRING DeviceString ) /*++ Routine Description: Creates a symbolic-link to the NAT's device-object so the NAT can be opened by a user-mode process. Arguments: DeviceString - Unicode name of the NAT's device-object. Return Value: none. --*/ { UNICODE_STRING symLinkString; PAGED_CODE(); RtlInitUnicodeString(&symLinkString, ExternalName); IoCreateSymbolicLink(&symLinkString, DeviceString); } // NatCreateExternalNaming VOID NatDeleteExternalNaming( VOID ) /*++ Routine Description: Deletes the Win32 symbolic-link to the NAT's device-object Arguments: Return Value: none. --*/ { UNICODE_STRING symLinkString; PAGED_CODE(); RtlInitUnicodeString(&symLinkString, ExternalName); IoDeleteSymbolicLink(&symLinkString); } // NatDeleteExternalNaming NTSTATUS NatInitializeDriver( VOID ) /*++ Routine Description: Performs initialization of the driver's structures. Arguments: none. Return Value: NTSTATUS - success/error code. --*/ { OBJECT_ATTRIBUTES ObjectAttributes; HANDLE ParametersKey; NTSTATUS status; NTSTATUS status2; UNICODE_STRING UnicodeString; IO_STATUS_BLOCK IoStatus; CALLTRACE(("NatInitializeDriver\n")); // // Set up global synchronization objects // InitializeComponentReference(&ComponentReference, NatCleanupDriver); // // Obtain the IP and TCP driver device-objects // RtlInitUnicodeString(&UnicodeString, DD_IP_DEVICE_NAME); status = IoGetDeviceObjectPointer( &UnicodeString, SYNCHRONIZE|GENERIC_READ|GENERIC_WRITE, &IpFileObject, &IpDeviceObject ); if (!NT_SUCCESS(status)) { ERROR(("NatInitializeDriver: error %X getting IP object\n", status)); return status; } RtlInitUnicodeString(&UnicodeString, DD_TCP_DEVICE_NAME); status = IoGetDeviceObjectPointer( &UnicodeString, SYNCHRONIZE|GENERIC_READ|GENERIC_WRITE, &TcpFileObject, &TcpDeviceObject ); if (!NT_SUCCESS(status)) { ERROR(("NatInitializeDriver: error %X getting TCP object\n", status)); return status; } // // Open Tcp Kernel Device // InitializeObjectAttributes( &ObjectAttributes, &UnicodeString, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile( &TcpDeviceHandle, GENERIC_READ, &ObjectAttributes, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, 0, NULL, 0); if ( !NT_SUCCESS(status) ) { ERROR(("ZwCreateFile failed (0x%08X)\n", status)); } ObReferenceObject(IpDeviceObject); ObReferenceObject(TcpDeviceObject); // // Initialize all object-modules // NatInitializeTimerManagement(); NatInitializeMappingManagement(); NatInitializeDirectorManagement(); NatInitializeEditorManagement(); NatInitializeRedirectManagement(); NatInitializeDynamicTicketManagement(); NatInitializeIcmpManagement(); NatInitializeRawIpManagement(); NatInitializeInterfaceManagement(); #if 0 status = NatInitializeAddressManagement(); if (!NT_SUCCESS(status)) { return status; } #endif NatInitializePacketManagement(); NatInitializeNotificationManagement(); #if NAT_WMI NatInitializeWMI(); #endif // // Initialize NAT-provided editors. // status = NatInitializePptpManagement(); if (!NT_SUCCESS(status)) { return status; } // // Commence translation of packets, and start the periodic timer. // status = NatInitiateTranslation(); // // Read optional registry settings. // The user may customize the range of ports used by modifying // the reserved-ports setting in the registry. // We now check to see if there is such a value, // and if so, we use it as our reserved-port range. // // The user may also specify that inbound non-unicast traffic // is allowed on a firewalled interface. // // N.B. Failures here are not returned to the caller. // RtlInitUnicodeString(&UnicodeString, IpNatParametersPath); InitializeObjectAttributes( &ObjectAttributes, &UnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); status2 = ZwOpenKey(&ParametersKey, KEY_READ, &ObjectAttributes); if (NT_SUCCESS(status2)) { UCHAR Buffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION)]; ULONG EndPort; PWCHAR p; ULONG StartPort; PKEY_VALUE_PARTIAL_INFORMATION Value = NULL; ULONG ValueLength; // // First check for allowed non-unicast traffic. // RtlInitUnicodeString( &UnicodeString, AllowInboundNonUnicastTrafficName ); status2 = ZwQueryValueKey( ParametersKey, &UnicodeString, KeyValuePartialInformation, (PKEY_VALUE_PARTIAL_INFORMATION)Buffer, sizeof(Buffer), &ValueLength ); if (NT_SUCCESS(status2) && REG_DWORD == ((PKEY_VALUE_PARTIAL_INFORMATION)Buffer)->Type) { AllowInboundNonUnicastTraffic = 1 == *((PULONG)((PKEY_VALUE_PARTIAL_INFORMATION)Buffer)->Data); } // // Check for reserved ports // do { RtlInitUnicodeString(&UnicodeString, ReservedPortsName); status2 = ZwQueryValueKey( ParametersKey, &UnicodeString, KeyValuePartialInformation, (PKEY_VALUE_PARTIAL_INFORMATION)Buffer, sizeof(Buffer), &ValueLength ); if (status2 != STATUS_BUFFER_OVERFLOW) { break; } Value = (PKEY_VALUE_PARTIAL_INFORMATION) ExAllocatePoolWithTag( PagedPool, ValueLength, NAT_TAG_RANGE_ARRAY ); if (!Value) { break; } status2 = ZwQueryValueKey( ParametersKey, &UnicodeString, KeyValuePartialInformation, (PKEY_VALUE_PARTIAL_INFORMATION)Value, ValueLength, &ValueLength ); if (!NT_SUCCESS(status2) || REG_SZ != Value->Type || L'\0' != *(PWCHAR) (Value->Data + (Value->DataLength - sizeof(WCHAR)))) { break; } // // The value should be in the format "xxx-yyy\0\0"; // read the first number // p = (PWCHAR)Value->Data; RtlInitUnicodeString(&UnicodeString, p); status2 = RtlUnicodeStringToInteger(&UnicodeString, 10, &StartPort); if (!NT_SUCCESS(status2)) { break; } // // Advance past '-' // while (*p && *p != L'-') { ++p; } if (*p != L'-') { break; } else { ++p; } // // Read second number // RtlInitUnicodeString(&UnicodeString, p); status2 = RtlUnicodeStringToInteger(&UnicodeString, 10, &EndPort); if (!NT_SUCCESS(status2)) { break; } // // Validate the resulting range // if (StartPort > 0 && StartPort < 65535 && EndPort > 0 && EndPort < 65535 && StartPort <= EndPort ) { ReservedPortsLowerRange = NTOHS((USHORT)StartPort); ReservedPortsUpperRange = NTOHS((USHORT)EndPort); } } while(FALSE); if (Value) { ExFreePool(Value); } ZwClose(ParametersKey); } return status; } // NatInitializeDriver NTSTATUS NatInitiateTranslation( VOID ) /*++ Routine Description: This routine is invoked on creation of the first interface, to launch the periodic timer and install the firewall hook. Arguments: none. Return Value: STATUS_SUCCESS if successful, error code otherwise. --*/ { CALLTRACE(("NatInitiateTranslation\n")); // // Launch the timer // NatStartTimer(); // // Install 'NatTranslate' as the firewall hook // return NatSetFirewallHook(TRUE); } // NatInitiateTranslation NTSTATUS NatSetFirewallHook( BOOLEAN Install ) /*++ Routine Description: This routine is called to set (Install==TRUE) or clear (Install==FALSE) the value of the firewall-callout function pointer in the IP driver. Arguments: Install - indicates whether to install or remove the hook. Return Value: NTSTATUS - indicates success/failure Environment: The routine assumes the caller is executing at PASSIVE_LEVEL. --*/ { IP_SET_FIREWALL_HOOK_INFO HookInfo; IO_STATUS_BLOCK IoStatus; PIRP Irp; TCP_RESERVE_PORT_RANGE PortRange; KEVENT LocalEvent; NTSTATUS status; CALLTRACE(("NatSetFirewallHook\n")); // // Register (or deregister) as a firewall // HookInfo.FirewallPtr = (IPPacketFirewallPtr)NatTranslatePacket; HookInfo.Priority = 1; HookInfo.Add = Install; KeInitializeEvent(&LocalEvent, SynchronizationEvent, FALSE); Irp = IoBuildDeviceIoControlRequest( IOCTL_IP_SET_FIREWALL_HOOK, IpDeviceObject, (PVOID)&HookInfo, sizeof(HookInfo), NULL, 0, FALSE, &LocalEvent, &IoStatus ); if (!Irp) { ERROR(("NatSetFirewallHook: IoBuildDeviceIoControlRequest=0\n")); return STATUS_UNSUCCESSFUL; } status = IoCallDriver(IpDeviceObject, Irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&LocalEvent, Executive, KernelMode, FALSE, NULL); KeResetEvent(&LocalEvent); status = IoStatus.Status; } if (!NT_SUCCESS(status)) { ERROR(("NatSetFirewallHook: IpSetFirewallHook=0x%08X\n", status)); return status; } if (ReservedPortsLowerRange != DEFAULT_START_PORT || ReservedPortsUpperRange != DEFAULT_END_PORT ) { return STATUS_SUCCESS; } // // Reserve (or unreserve) our port-range // // N.B. The IOCTL expects host-order numbers and we store the range // in network order, so do a swap before reserving the ports. // PortRange.LowerRange = NTOHS(DEFAULT_START_PORT); PortRange.UpperRange = NTOHS(DEFAULT_END_PORT); Irp = IoBuildDeviceIoControlRequest( Install ? IOCTL_TCP_RESERVE_PORT_RANGE : IOCTL_TCP_UNRESERVE_PORT_RANGE, TcpDeviceObject, (PVOID)&PortRange, sizeof(PortRange), NULL, 0, FALSE, &LocalEvent, &IoStatus ); if (!Irp) { ERROR(("NatSetFirewallHook: IoBuildDeviceIoControlRequest(2)=0\n")); return STATUS_UNSUCCESSFUL; } status = IoCallDriver(TcpDeviceObject, Irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&LocalEvent, Executive, KernelMode, FALSE, NULL); status = IoStatus.Status; } if (!NT_SUCCESS(status)) { ERROR(("NatSetFirewallHook: Tcp(Un)ReservePortRange=0x%08X\n", status)); } return status; } // NatSetFirewallHook VOID NatTerminateTranslation( VOID ) /*++ Routine Description: On cleanup of the last interface, this routine is invoked to stop the periodic timer and de-install the firewall hook. Arguments: none. Return Value: none. --*/ { CALLTRACE(("NatTerminateTranslation\n")); NatSetFirewallHook(FALSE); } // NatTerminateTranslation VOID NatUnloadDriver( IN PDRIVER_OBJECT DriverObject ) /*++ Routine Description: Performs cleanup for the NAT. Arguments: DriverObject - reference to the NAT's driver-object Return Value: --*/ { PNAT_EDITOR Editor; PLIST_ENTRY List; CALLTRACE(("NatUnloadDriver\n")); // // Stop translation and clear the periodic timer // NatTerminateTranslation(); // // Stop the route-change-notification in our packet-management and // address-management modules. // This forces completion of the route-change and address-change IRPs, // which in turn releases component-references which would otherwise not // drop until a route-change and address-change occurred. // NatShutdownNotificationManagement(); NatShutdownPacketManagement(); #if 0 NatShutdownAddressManagement(); #endif // // Drop our self-reference and wait for all activity to cease. // ReleaseInitialComponentReference(&ComponentReference, TRUE); // // Tear down our Win32-namespace symbolic link // NatDeleteExternalNaming(); // // Delete the NAT's device object // IoDeleteDevice(DriverObject->DeviceObject); // // Shutdown object modules // #if NAT_WMI NatShutdownWMI(); if( NatRegistryPath.Buffer ) { ExFreePool( NatRegistryPath.Buffer ); RtlInitUnicodeString( &NatRegistryPath, NULL ); } #endif NatShutdownPptpManagement(); NatShutdownTimerManagement(); NatShutdownMappingManagement(); NatShutdownEditorManagement(); NatShutdownDirectorManagement(); NatShutdownDynamicTicketManagement(); NatShutdownRawIpManagement(); NatShutdownIcmpManagement(); NatShutdownInterfaceManagement(); // // Release references to the IP and TCP driver objects // ObDereferenceObject((PVOID)IpFileObject); ObDereferenceObject(IpDeviceObject); ObDereferenceObject((PVOID)TcpFileObject); ObDereferenceObject(TcpDeviceObject); if (TcpDeviceHandle) { ZwClose(TcpDeviceHandle); TcpDeviceHandle = NULL; } DeleteComponentReference(&ComponentReference); } // NatUnloadDriver