From patchwork Mon Jan 7 07:15:03 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ard Biesheuvel X-Patchwork-Id: 154874 Delivered-To: patch@linaro.org Received: by 2002:a2e:299d:0:0:0:0:0 with SMTP id p29-v6csp3192752ljp; Sun, 6 Jan 2019 23:15:35 -0800 (PST) X-Google-Smtp-Source: ALg8bN7LJIufdzQh1aQcX8GEWoqPRVPmVZp3AP2Whbnp838qR59H0Eadd+af4rXdT64It9RqGly5 X-Received: by 2002:a63:8b41:: with SMTP id j62mr10132969pge.182.1546845335750; Sun, 06 Jan 2019 23:15:35 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1546845335; cv=none; d=google.com; s=arc-20160816; b=znbR1bdbN7V1qPvcwjaRXeh2V+5nvBh8Um7hysCbX/sKATyJOtqnLImnoG/g0tmXAd iNLx609WslhvNXwzJSdRIc+D9MpEVQDoftEbNVTVPbq2dvPd9iFl2tC2YvljCph9fA59 OmZsZWPBOf3YQZaQGnv0gXOk4lhrWpDqH1XGn7M9tBzC04KnYOUcXIDxX5wYAF2eZgNd tHnVmETGePfZZffdaQNVLNaGcjZEGySryiGtKrzx85RXJSk0eMgnrGzB3tTRP9v4O5S9 pUL3/w3633BT40ASBhUZFKSte03/kqfkIKzspceotVOHrCSKKeF2vgeWo5kJCj42y1KM 8mFQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:delivered-to; bh=UdtSC5aF5oVp6HpRw1bHVJ6RkBpDwH1Oe/rHlqULRlc=; b=wzdnca767QBAeUKl3qJzNdiIN16dwOyyEnO7AX9zI+6IrlmF4Jcj6gbcP4vuYKMU06 IFgGrhDwmpectVxQpvluTQxFqT7DsoU/Ad62mf+5xd4qrAevqFOmU7wcAf0VWz0JYBGD G+mG6AyqwssebL4D4TpO0AULxsJmFXE/7Bo0H0i59sy97abYrUpRB5WlMDMwwGxPeoqb 1pSdr2ZFol3PEXXQQiO5G7AOtwAAiGqv66aH/GFpoODjrqYXGmmAegPu/M1gw2ERJBKI kdX3hH0taT8QGDmFvbW6UvhXzkvOeUAo7/eM6+28mJYm6+UUK2efgqjkeeyHJ7lAhgmE B5ZQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@linaro.org header.s=google header.b="As3/zRo7"; spf=pass (google.com: best guess record for domain of edk2-devel-bounces@lists.01.org designates 2001:19d0:306:5::1 as permitted sender) smtp.mailfrom=edk2-devel-bounces@lists.01.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from ml01.01.org (ml01.01.org. [2001:19d0:306:5::1]) by mx.google.com with ESMTPS id i5si18622pfo.189.2019.01.06.23.15.35 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 06 Jan 2019 23:15:35 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of edk2-devel-bounces@lists.01.org designates 2001:19d0:306:5::1 as permitted sender) client-ip=2001:19d0:306:5::1; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@linaro.org header.s=google header.b="As3/zRo7"; spf=pass (google.com: best guess record for domain of edk2-devel-bounces@lists.01.org designates 2001:19d0:306:5::1 as permitted sender) smtp.mailfrom=edk2-devel-bounces@lists.01.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: from [127.0.0.1] (localhost [IPv6:::1]) by ml01.01.org (Postfix) with ESMTP id 4E8D2211B112C; Sun, 6 Jan 2019 23:15:29 -0800 (PST) X-Original-To: edk2-devel@lists.01.org Delivered-To: edk2-devel@lists.01.org Received-SPF: Pass (sender SPF authorized) identity=mailfrom; client-ip=2a00:1450:4864:20::541; helo=mail-ed1-x541.google.com; envelope-from=ard.biesheuvel@linaro.org; receiver=edk2-devel@lists.01.org Received: from mail-ed1-x541.google.com (mail-ed1-x541.google.com [IPv6:2a00:1450:4864:20::541]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ml01.01.org (Postfix) with ESMTPS id 48834211B113B for ; Sun, 6 Jan 2019 23:15:27 -0800 (PST) Received: by mail-ed1-x541.google.com with SMTP id y20so36883315edw.9 for ; Sun, 06 Jan 2019 23:15:27 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=ykripWntC//V83Pxjhm+0rNxHF0mt7ZtYpjgN+s6F5U=; b=As3/zRo7EU0PImCA36IxwveVudL1neKmXn+sWGswY7rHstyHuWn8XFUwC2GJQtB/Hn CQaP1cuzbeKm5FOjmAYvII+Afrkngfn3KzkQoMQ0WopvxRFpdnJ9K3s+hg8FJEGrfIK9 bJHwUM4kmuZmGU0IPiZJOsFQPzTWN8sjsBdHE= 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=ykripWntC//V83Pxjhm+0rNxHF0mt7ZtYpjgN+s6F5U=; b=Sjh1kWgWpUt1a+4/+c/ATqQTOrvIXsaAP3kGhRHEtqrTbNMJml6jaWaHFzdLzxkIKp Ykf1+ejCMbXptQA6yDGQrh0AcOSA7tknK6PjBR6vypT79FGzT6dWNNR3/qMZ9lIOdVTj pOJkvTk1o54YnTUKM3AuYHYO/AfPJKBVycDNDamKwvc8NbH652+dZ0vA+NwkZcu/4v9k kAkkoUjMGLuArHyWDD1EG175mdG3c6ctxPN+fS9Z/SnrjsggGqdS4b+ko8Xm57ZVqTmj cVKX+5m69DjgliWD0X/rKQXp219MhIWKYa/Anin72HOJczoAdhNJqDz15LyJhRWG9uDZ ltNg== X-Gm-Message-State: AA+aEWY7nzQj7WYu1Zw2XfI6uQZAk7VzEgVLbDMs5Uz4WRU6I5LZOayX wo8AZP8rBNr+033DX6D9uXO93S1IFogtUg== X-Received: by 2002:a50:a622:: with SMTP id d31mr57215509edc.228.1546845325455; Sun, 06 Jan 2019 23:15:25 -0800 (PST) Received: from chuckie.home ([2a01:cb1d:112:6f00:58f2:776e:9e23:a7ca]) by smtp.gmail.com with ESMTPSA id t9sm30263693edd.25.2019.01.06.23.15.23 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 06 Jan 2019 23:15:24 -0800 (PST) From: Ard Biesheuvel To: edk2-devel@lists.01.org Date: Mon, 7 Jan 2019 08:15:03 +0100 Message-Id: <20190107071504.2431-5-ard.biesheuvel@linaro.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190107071504.2431-1-ard.biesheuvel@linaro.org> References: <20190107071504.2431-1-ard.biesheuvel@linaro.org> MIME-Version: 1.0 Subject: [edk2] [PATCH 4/5] ArmPkg/ArmMmuLib AARCH64: add support for read-only page tables X-BeenThere: edk2-devel@lists.01.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: EDK II Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: edk2-devel-bounces@lists.01.org Sender: "edk2-devel" As a hardening measure, implement support for remapping all page tables read-only at a certain point during the boot (end of DXE is the most appropriate trigger). This should make it a lot more difficult to take advantage of write exploits to defeat authentication checks, since the attacker can no longer manipulate the page tables directly. To allow the page tables to still be manipulated, make use of the existing code to manipulate live entries: this drops into assembler with interrupts off, and disables the MMU for a brief moment to avoid causing TLB conflicts. Since page tables are writable with the MMU off, we can reuse this code to still manipulate the page tables after we updated the CPU mappings to be read-only. Contributed-under: TianoCore Contribution Agreement 1.1 Signed-off-by: Ard Biesheuvel --- ArmPkg/Include/Library/ArmMmuLib.h | 6 + ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c | 119 ++++++++++++++++++-- ArmPkg/Library/ArmMmuLib/Arm/ArmMmuLibCore.c | 8 ++ 3 files changed, 123 insertions(+), 10 deletions(-) -- 2.20.1 _______________________________________________ edk2-devel mailing list edk2-devel@lists.01.org https://lists.01.org/mailman/listinfo/edk2-devel diff --git a/ArmPkg/Include/Library/ArmMmuLib.h b/ArmPkg/Include/Library/ArmMmuLib.h index d2725810f1c6..f0832b91bf17 100644 --- a/ArmPkg/Include/Library/ArmMmuLib.h +++ b/ArmPkg/Include/Library/ArmMmuLib.h @@ -70,4 +70,10 @@ ArmSetMemoryAttributes ( IN UINT64 Attributes ); +VOID +EFIAPI +MapAllPageTablesReadOnly ( + VOID + ); + #endif diff --git a/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c b/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c index b59c081a7e49..cefaad9961ea 100644 --- a/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c +++ b/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c @@ -28,6 +28,8 @@ // We use this index definition to define an invalid block entry #define TT_ATTR_INDX_INVALID ((UINT32)~0) +STATIC BOOLEAN mReadOnlyPageTables; + STATIC UINT64 ArmMemoryAttributeToPageAttribute ( @@ -137,6 +139,9 @@ ReplaceLiveEntry ( IN UINT64 Address ) { + if (*Entry == Value) { + return; + } if (!ArmMmuEnabled ()) { *Entry = Value; } else { @@ -181,7 +186,8 @@ GetBlockEntryListFromAddress ( IN UINT64 RegionStart, OUT UINTN *TableLevel, IN OUT UINT64 *BlockEntrySize, - OUT UINT64 **LastBlockEntry + OUT UINT64 **LastBlockEntry, + OUT BOOLEAN *NewPageTablesAllocated ) { UINTN RootTableLevel; @@ -292,6 +298,8 @@ GetBlockEntryListFromAddress ( return NULL; } + *NewPageTablesAllocated = TRUE; + // Populate the newly created lower level table SubTableBlockEntry = TranslationTable; for (Index = 0; Index < TT_ENTRY_COUNT; Index++) { @@ -316,10 +324,18 @@ GetBlockEntryListFromAddress ( return NULL; } + *NewPageTablesAllocated = TRUE; + ZeroMem (TranslationTable, TT_ENTRY_COUNT * sizeof(UINT64)); // Fill the new BlockEntry with the TranslationTable - *BlockEntry = ((UINTN)TranslationTable & TT_ADDRESS_MASK_DESCRIPTION_TABLE) | TT_TYPE_TABLE_ENTRY; + if (!mReadOnlyPageTables) { + *BlockEntry = (UINTN)TranslationTable | TT_TYPE_TABLE_ENTRY; + } else { + ReplaceLiveEntry (BlockEntry, + (UINTN)TranslationTable | TT_TYPE_TABLE_ENTRY, + RegionStart); + } } } } @@ -345,7 +361,8 @@ UpdateRegionMapping ( IN UINT64 RegionStart, IN UINT64 RegionLength, IN UINT64 Attributes, - IN UINT64 BlockEntryMask + IN UINT64 BlockEntryMask, + OUT BOOLEAN *ReadOnlyRemapDone ) { UINT32 Type; @@ -353,6 +370,7 @@ UpdateRegionMapping ( UINT64 *LastBlockEntry; UINT64 BlockEntrySize; UINTN TableLevel; + BOOLEAN NewPageTablesAllocated; // Ensure the Length is aligned on 4KB boundary if ((RegionLength == 0) || ((RegionLength & (SIZE_4KB - 1)) != 0)) { @@ -360,11 +378,13 @@ UpdateRegionMapping ( return EFI_INVALID_PARAMETER; } + NewPageTablesAllocated = FALSE; do { // Get the first Block Entry that matches the Virtual Address and also the information on the Table Descriptor // such as the the size of the Block Entry and the address of the last BlockEntry of the Table Descriptor BlockEntrySize = RegionLength; - BlockEntry = GetBlockEntryListFromAddress (RootTable, RegionStart, &TableLevel, &BlockEntrySize, &LastBlockEntry); + BlockEntry = GetBlockEntryListFromAddress (RootTable, RegionStart, + &TableLevel, &BlockEntrySize, &LastBlockEntry, &NewPageTablesAllocated); if (BlockEntry == NULL) { // GetBlockEntryListFromAddress() return NULL when it fails to allocate new pages from the Translation Tables return EFI_OUT_OF_RESOURCES; @@ -378,10 +398,16 @@ UpdateRegionMapping ( do { // Fill the Block Entry with attribute and output block address - *BlockEntry &= BlockEntryMask; - *BlockEntry |= (RegionStart & TT_ADDRESS_MASK_BLOCK_ENTRY) | Attributes | Type; + if (!mReadOnlyPageTables) { + *BlockEntry &= BlockEntryMask; + *BlockEntry |= (RegionStart & TT_ADDRESS_MASK_BLOCK_ENTRY) | Attributes | Type; - ArmUpdateTranslationTableEntry (BlockEntry, (VOID *)RegionStart); + ArmUpdateTranslationTableEntry (BlockEntry, (VOID *)RegionStart); + } else { + ReplaceLiveEntry (BlockEntry, + (*BlockEntry & BlockEntryMask) | (RegionStart & TT_ADDRESS_MASK_BLOCK_ENTRY) | Attributes | Type, + RegionStart); + } // Go to the next BlockEntry RegionStart += BlockEntrySize; @@ -397,9 +423,79 @@ UpdateRegionMapping ( } while ((RegionLength >= BlockEntrySize) && (BlockEntry <= LastBlockEntry)); } while (RegionLength != 0); + // if we have switched to read-only page tables, find the newly allocated ones + // and update their permissions + if (mReadOnlyPageTables && NewPageTablesAllocated) { + MapAllPageTablesReadOnly (); + if (ReadOnlyRemapDone) { + *ReadOnlyRemapDone = TRUE; + } + } + return EFI_SUCCESS; } +STATIC +BOOLEAN +EFIAPI +MapPageTableReadOnlyRecursive ( + IN UINT64 *RootTable, + IN UINT64 *TableEntry, + IN UINTN NumEntries, + IN UINTN TableLevel + ) +{ + EFI_STATUS Status; + BOOLEAN Done; + + // + // The UpdateRegionMapping () call in this function may recurse into + // MapAllPageTablesReadOnly () if it allocates any page tables. When + // this happens, there is little point in proceeding here, so let's + // bail early in that case. + // + Done = FALSE; + Status = UpdateRegionMapping (RootTable, (UINT64)TableEntry, EFI_PAGE_SIZE, + TT_AP_RO_RO, ~TT_ADDRESS_MASK_BLOCK_ENTRY, &Done); + ASSERT_EFI_ERROR (Status); + + if (TableLevel == 3) { + return Done; + } + + // go over the table and recurse for each table type entry + while (!Done && NumEntries--) { + if ((*TableEntry & TT_TYPE_MASK) == TT_TYPE_TABLE_ENTRY) { + Done = MapPageTableReadOnlyRecursive (RootTable, + (UINT64 *)(*TableEntry & TT_ADDRESS_MASK_DESCRIPTION_TABLE), + TT_ENTRY_COUNT, TableLevel + 1); + } + TableEntry++; + } + return Done; +} + +VOID +EFIAPI +MapAllPageTablesReadOnly ( + VOID + ) +{ + UINTN T0SZ; + UINTN RootTableEntryCount; + UINTN RootLevel; + UINT64 *RootTable; + + mReadOnlyPageTables = TRUE; + + T0SZ = ArmGetTCR () & TCR_T0SZ_MASK; + GetRootTranslationTableInfo (T0SZ, &RootLevel, &RootTableEntryCount); + RootTable = ArmGetTTBR0BaseAddress (); + + MapPageTableReadOnlyRecursive (RootTable, RootTable, RootTableEntryCount, + RootLevel); +} + STATIC EFI_STATUS FillTranslationTable ( @@ -412,7 +508,8 @@ FillTranslationTable ( MemoryRegion->VirtualBase, MemoryRegion->Length, ArmMemoryAttributeToPageAttribute (MemoryRegion->Attributes) | TT_AF, - 0 + 0, + NULL ); } @@ -494,7 +591,8 @@ ArmSetMemoryAttributes ( BaseAddress, Length, PageAttributes, - PageAttributeMask); + PageAttributeMask, + NULL); if (EFI_ERROR (Status)) { return Status; } @@ -516,7 +614,8 @@ SetMemoryRegionAttribute ( RootTable = ArmGetTTBR0BaseAddress (); - Status = UpdateRegionMapping (RootTable, BaseAddress, Length, Attributes, BlockEntryMask); + Status = UpdateRegionMapping (RootTable, BaseAddress, Length, Attributes, + BlockEntryMask, NULL); if (EFI_ERROR (Status)) { return Status; } diff --git a/ArmPkg/Library/ArmMmuLib/Arm/ArmMmuLibCore.c b/ArmPkg/Library/ArmMmuLib/Arm/ArmMmuLibCore.c index bffab83d4fd0..9a75026e2919 100644 --- a/ArmPkg/Library/ArmMmuLib/Arm/ArmMmuLibCore.c +++ b/ArmPkg/Library/ArmMmuLib/Arm/ArmMmuLibCore.c @@ -844,3 +844,11 @@ ArmMmuBaseLibConstructor ( { return RETURN_SUCCESS; } + +VOID +EFIAPI +MapAllPageTablesReadOnly ( + VOID + ) +{ +}