From patchwork Wed May 26 21:01:46 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Kozlyuk X-Patchwork-Id: 93452 X-Patchwork-Delegate: thomas@monjalon.net Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 7F695A0546; Wed, 26 May 2021 23:02:18 +0200 (CEST) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 638144110E; Wed, 26 May 2021 23:02:03 +0200 (CEST) Received: from mail-lf1-f52.google.com (mail-lf1-f52.google.com [209.85.167.52]) by mails.dpdk.org (Postfix) with ESMTP id 38B9D410E6 for ; Wed, 26 May 2021 23:01:59 +0200 (CEST) Received: by mail-lf1-f52.google.com with SMTP id j6so4597207lfr.11 for ; Wed, 26 May 2021 14:01:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=mBqvnwph9Yp2mLQXehDOCd70c64arm+VzlOlgzSx+3s=; b=DspMuVWeRTQ6ApYuoaTlp1qBUGehaKnoJoRUbvd4yf36wnizFkxilSZjtYdgRVbGL4 A0RR3x6TgjPbvkOixUW90XKetXfC/6LBJepB3i9k52BxUhj7Gjdlo0G+59/oQ0/Ad7BW GfVRaEF9KWUgz02ow0Kcv55IxAxvshLUX2VPmQNGbfWETByPPkgPfP5R7vjztDCK2Ad4 Y3XN4guQ4Tekynz4hCuQUueQnTy2qXsZDD53wmIxHELtrVCxv8q/27rLeiEMwe5P4alw cq6nNgAxkHDu1Ls7QFGaPXwFORycyLgfbvKI+cKLDOHdzB/0J1wUM7FKyjdmaaM8/6Vg p5Dg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=mBqvnwph9Yp2mLQXehDOCd70c64arm+VzlOlgzSx+3s=; b=VZHu8gN6aAlK1hbUliylFPItKTNxOx742Ez8zrthnjtvkpeg0JHegt7UnVfGLOEOOp aazI4Mxnv7yM6fH44FNdCw8QsVIHPB1bZHyav+mL6CzvpFSR9mdqKXvW3VssH4/epc4w YZi0YJZQU4oG3GZUOwusHx89qSzEHAqNXxBPZbjrsvg2l1uzTEJV44Vn5C2Al26KUob8 DZtD1XOsKGQmR81yATAPm4oENdt+wnZecWTIssgU5Oc88o4Y5Pa5+lBgHhZ1M5mkrdpG daSAtJkCopacm4ZoWg3ds7NhrTYzPsCbGABrsxKwlJZgOuh4cH/qUUepkJutg47SIwt4 LG8g== X-Gm-Message-State: AOAM533mBoXtd8nSgQX6ikjSMQE2SZLrGB1YZwLErmegdI7B7MB1C7mA VO+tpysEvzUPxHN10gJrnnnfEe86bSGhgw== X-Google-Smtp-Source: ABdhPJwlyPBJvELGAx/ysP6Z0Gn0tbCyUiRPVAucF1Jcuqvd/qF8vEAU5ORKJQOnoyi9CYI41FxxUQ== X-Received: by 2002:ac2:4281:: with SMTP id m1mr36595lfh.164.1622062918338; Wed, 26 May 2021 14:01:58 -0700 (PDT) Received: from localhost.localdomain (broadband-37-110-65-23.ip.moscow.rt.ru. [37.110.65.23]) by smtp.gmail.com with ESMTPSA id u28sm13205lfk.172.2021.05.26.14.01.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 May 2021 14:01:57 -0700 (PDT) From: Dmitry Kozlyuk To: dev@dpdk.org Cc: Dmitry Malloy , Narcisa Ana Maria Vasile , Pallavi Kadam , Tyler Retzlaff , Nick Connolly , Dmitry Kozlyuk Date: Thu, 27 May 2021 00:01:46 +0300 Message-Id: <20210526210147.1287-4-dmitry.kozliuk@gmail.com> X-Mailer: git-send-email 2.29.3 In-Reply-To: <20210526210147.1287-1-dmitry.kozliuk@gmail.com> References: <20210501171837.13282-1-dmitry.kozliuk@gmail.com> <20210526210147.1287-1-dmitry.kozliuk@gmail.com> MIME-Version: 1.0 Subject: [dpdk-dev] [kmods PATCH v2 3/4] windows/virt2phys: add limits against resource exhaustion X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Sender: "dev" Tracked processes are enumerated in process creation/termination callback, making it O(N = number of tracked processes). A malicious user could cause system-wide slowdown of process termination by making just one call to virt2phys from many processes. Limit the number of tracked processes by `ProcessCountLimit` items. Any process with access to virt2phys could exhaust RAM for all the system by locking it. It can also exhaust RAM needed for other processes with access to virt2phys. Limit amount of memory locked by each tracked process with `ProcessMemoryLimitMB`. All limits are read from registry at driver load. Each limit can be turned off by setting it to 0. Document limits and their non-zero defaults for administrators. Signed-off-by: Dmitry Kozlyuk --- windows/virt2phys/README.md | 38 +++++++++++++++ windows/virt2phys/virt2phys.c | 73 ++++++++++++++++++++++++++++- windows/virt2phys/virt2phys_logic.c | 46 +++++++++++++----- windows/virt2phys/virt2phys_logic.h | 9 +++- 4 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 windows/virt2phys/README.md diff --git a/windows/virt2phys/README.md b/windows/virt2phys/README.md new file mode 100644 index 0000000..04012ff --- /dev/null +++ b/windows/virt2phys/README.md @@ -0,0 +1,38 @@ +Virtual to Physical Address Translator +====================================== + +Purpose and Operation +--------------------- + +``virt2phys`` driver allows user-mode processes to obtain physical address +of a given virtual address in their address space. +Virtual addresses must belong to regions from process private working set. +These regions must be physically contiguous. +The driver ensures that memory regions with translated addresses +are not swapped out as long as the process has access to this memory. + +It is not safe to administratively unload the driver +while there are processes that have used virt2phys to translate addresses. +Doing so will permanently leak RAM occupied by all memory regions +that contain translated addresses. +Terminate all such processes before unloading the driver. + +Configuration +------------- + +``virt2phys`` is configured at loading time via registry key +``HKLM\SYSTEM\ControlSet001\Services\virt2phys\Parameters``. + +* ``ProcessCountLimit`` (default 16) + + Maximum number of processes that can have access to memory regions + with translated addresses. When this limit is reached, no more processes + can translate addresses using ``virt2phys``. Large number of tracked + processes may slow down system operation. Set limit to 0 to disable it. + +* ``ProcessMemoryLimitMB`` (default 16384, i.e. 16 GB) + + Maximum amount of memory in all regions that contain translated addresses, + total per process. When this limit is reached, the process can not translate + addresses from new regions. Large values can cause RAM exhaustion. + Set limit to 0 to disable it. \ No newline at end of file diff --git a/windows/virt2phys/virt2phys.c b/windows/virt2phys/virt2phys.c index bf0c300..44204c9 100644 --- a/windows/virt2phys/virt2phys.c +++ b/windows/virt2phys/virt2phys.c @@ -14,15 +14,22 @@ EVT_WDF_DRIVER_UNLOAD virt2phys_driver_unload; EVT_WDF_DRIVER_DEVICE_ADD virt2phys_driver_EvtDeviceAdd; EVT_WDF_IO_IN_CALLER_CONTEXT virt2phys_device_EvtIoInCallerContext; +static NTSTATUS virt2phys_load_params( + WDFDRIVER driver, struct virt2phys_params *params); static VOID virt2phys_on_process_event( HANDLE parent_id, HANDLE process_id, BOOLEAN create); +static const ULONG PROCESS_COUNT_LIMIT_DEF = 1 << 4; +static const ULONG PROCESS_MEMORY_LIMIT_DEF = 16 * (1 << 10); /* MB */ + _Use_decl_annotations_ NTSTATUS DriverEntry(PDRIVER_OBJECT driver_object, PUNICODE_STRING registry_path) { WDF_DRIVER_CONFIG config; WDF_OBJECT_ATTRIBUTES attributes; + WDFDRIVER driver; + struct virt2phys_params params; NTSTATUS status; PAGED_CODE(); @@ -32,11 +39,15 @@ DriverEntry(PDRIVER_OBJECT driver_object, PUNICODE_STRING registry_path) config.EvtDriverUnload = virt2phys_driver_unload; status = WdfDriverCreate( driver_object, registry_path, - &attributes, &config, WDF_NO_HANDLE); + &attributes, &config, &driver); + if (!NT_SUCCESS(status)) + return status; + + status = virt2phys_load_params(driver, ¶ms); if (!NT_SUCCESS(status)) return status; - status = virt2phys_init(); + status = virt2phys_init(¶ms); if (!NT_SUCCESS(status)) return status; @@ -58,6 +69,64 @@ DriverEntry(PDRIVER_OBJECT driver_object, PUNICODE_STRING registry_path) return status; } +static NTSTATUS +virt2phys_read_param(WDFKEY key, PCUNICODE_STRING name, ULONG *value, + ULONG def) +{ + NTSTATUS status; + + status = WdfRegistryQueryULong(key, name, value); + if (status == STATUS_OBJECT_NAME_NOT_FOUND) { + *value = def; + status = STATUS_SUCCESS; + } + return status; +} + +static NTSTATUS +virt2phys_read_mb(WDFKEY key, PCUNICODE_STRING name, ULONG64 *bytes, + ULONG def_mb) +{ + ULONG mb; + NTSTATUS status; + + status = virt2phys_read_param(key, name, &mb, def_mb); + if (NT_SUCCESS(status)) + *bytes = (ULONG64)mb * (1ULL << 20); + return status; +} + +static NTSTATUS +virt2phys_load_params(WDFDRIVER driver, struct virt2phys_params *params) +{ + static DECLARE_CONST_UNICODE_STRING( + process_count_limit, L"ProcessCountLimit"); + static DECLARE_CONST_UNICODE_STRING( + process_memory_limit, L"ProcessMemoryLimitMB"); + + WDFKEY key; + NTSTATUS status; + + status = WdfDriverOpenParametersRegistryKey( + driver, KEY_READ, WDF_NO_OBJECT_ATTRIBUTES, &key); + if (!NT_SUCCESS(status)) + return status; + + status = virt2phys_read_param(key, &process_count_limit, + ¶ms->process_count_limit, PROCESS_COUNT_LIMIT_DEF); + if (!NT_SUCCESS(status)) + goto cleanup; + + status = virt2phys_read_mb(key, &process_memory_limit, + ¶ms->process_memory_limit, PROCESS_MEMORY_LIMIT_DEF); + if (!NT_SUCCESS(status)) + goto cleanup; + +cleanup: + WdfRegistryClose(key); + return status; +} + _Use_decl_annotations_ VOID virt2phys_driver_unload(WDFDRIVER driver) diff --git a/windows/virt2phys/virt2phys_logic.c b/windows/virt2phys/virt2phys_logic.c index a27802c..37b4dd4 100644 --- a/windows/virt2phys/virt2phys_logic.c +++ b/windows/virt2phys/virt2phys_logic.c @@ -11,6 +11,7 @@ struct virt2phys_process { HANDLE id; LIST_ENTRY next; SINGLE_LIST_ENTRY blocks; + ULONG64 memory; }; struct virt2phys_block { @@ -18,7 +19,9 @@ struct virt2phys_block { SINGLE_LIST_ENTRY next; }; +static struct virt2phys_params g_params; static LIST_ENTRY g_processes; +static LONG g_process_count; static PKSPIN_LOCK g_lock; struct virt2phys_block * @@ -112,7 +115,7 @@ virt2phys_process_find_block(struct virt2phys_process *process, PVOID virt) } NTSTATUS -virt2phys_init(void) +virt2phys_init(const struct virt2phys_params *params) { g_lock = ExAllocatePoolZero(NonPagedPool, sizeof(*g_lock), 'gp2v'); if (g_lock == NULL) @@ -120,6 +123,7 @@ virt2phys_init(void) InitializeListHead(&g_processes); + g_params = *params; return STATUS_SUCCESS; } @@ -165,8 +169,10 @@ virt2phys_process_cleanup(HANDLE process_id) process = virt2phys_process_detach(process_id); KeReleaseSpinLock(g_lock, irql); - if (process != NULL) + if (process != NULL) { virt2phys_process_free(process, TRUE); + InterlockedDecrement(&g_process_count); + } } static struct virt2phys_block * @@ -195,21 +201,38 @@ virt2phys_exceeeds(LONG64 count, ULONG64 limit) return limit > 0 && count > (LONG64)limit; } -static BOOLEAN +static NTSTATUS virt2phys_add_block(struct virt2phys_process *process, - struct virt2phys_block *block) + struct virt2phys_block *block, BOOLEAN *process_exists) { struct virt2phys_process *existing; + size_t size; existing = virt2phys_process_find(process->id); - if (existing == NULL) + *process_exists = existing != NULL; + if (existing == NULL) { + /* + * This check is done with the lock held so that's no race. + * Increment below must be atomic however, + * because decrement is done without holding the lock. + */ + if (virt2phys_exceeeds(g_process_count + 1, + g_params.process_count_limit)) + return STATUS_QUOTA_EXCEEDED; + InsertHeadList(&g_processes, &process->next); - else + InterlockedIncrement(&g_process_count); + } else process = existing; - PushEntryList(&process->blocks, &block->next); + size = MmGetMdlByteCount(block->mdl); + if (virt2phys_exceeeds(process->memory + size, + g_params.process_memory_limit)) + return STATUS_QUOTA_EXCEEDED; - return existing != NULL; + PushEntryList(&process->blocks, &block->next); + process->memory += size; + return STATUS_SUCCESS; } static NTSTATUS @@ -356,13 +379,14 @@ virt2phys_translate(PVOID virt, PHYSICAL_ADDRESS *phys) } KeAcquireSpinLock(g_lock, &irql); - tracked = virt2phys_add_block(process, block); + status = virt2phys_add_block(process, block, &tracked); KeReleaseSpinLock(g_lock, irql); /* Same process has been added concurrently, block attached to it. */ if (tracked && created) virt2phys_process_free(process, FALSE); - *phys = virt2phys_block_translate(block, virt); - return STATUS_SUCCESS; + if (NT_SUCCESS(status)) + *phys = virt2phys_block_translate(block, virt); + return status; } diff --git a/windows/virt2phys/virt2phys_logic.h b/windows/virt2phys/virt2phys_logic.h index 1582206..c8255f9 100644 --- a/windows/virt2phys/virt2phys_logic.h +++ b/windows/virt2phys/virt2phys_logic.h @@ -5,10 +5,17 @@ #ifndef VIRT2PHYS_LOGIC_H #define VIRT2PHYS_LOGIC_H +struct virt2phys_params { + /** Maximum number of tracked processes (0 = unlimited). */ + ULONG process_count_limit; + /** Maximum amount of memory locked by a process (0 = unlimited). */ + ULONG64 process_memory_limit; +}; + /** * Initialize internal data structures. */ -NTSTATUS virt2phys_init(void); +NTSTATUS virt2phys_init(const struct virt2phys_params *params); /** * Free memory allocated for internal data structures.