From: "Ning Feng via groups.io" <ning.feng=intel.com@groups.io>
To: devel@edk2.groups.io
Cc: Ning Feng <ning.feng@intel.com>
Subject: [edk2-devel] [PATCH] MedModulePkg/DxeIplPeim: Fix pagetable protection region in 5 level paging
Date: Tue, 12 Nov 2024 00:59:44 -0500 [thread overview]
Message-ID: <20241112055944.1820285-1-ning.feng@intel.com> (raw)
REF:https://bugzilla.tianocore.org/show_bug.cgi?id=4873
Currently the function does not cover the 5 level paging case. it will
casued pagetable protection region set incorrectly. This patch do the
enhancemant and with the patch protection region has been set correctly.
Signed-off-by: Ning Feng <ning.feng@intel.com>
---
.../Core/DxeIplPeim/Ia32/DxeLoadFunc.c | 2 +-
.../Core/DxeIplPeim/X64/VirtualMemory.c | 51 ++++++++++---------
.../Core/DxeIplPeim/X64/VirtualMemory.h | 15 +++---
.../UefiPayloadEntry/Ia32/DxeLoadFunc.c | 2 +-
.../UefiPayloadEntry/X64/VirtualMemory.c | 45 ++++++++--------
.../UefiPayloadEntry/X64/VirtualMemory.h | 15 +++---
6 files changed, 71 insertions(+), 59 deletions(-)
diff --git a/MdeModulePkg/Core/DxeIplPeim/Ia32/DxeLoadFunc.c b/MdeModulePkg/Core/DxeIplPeim/Ia32/DxeLoadFunc.c
index 65e9bdc99e..60878b4c1a 100644
--- a/MdeModulePkg/Core/DxeIplPeim/Ia32/DxeLoadFunc.c
+++ b/MdeModulePkg/Core/DxeIplPeim/Ia32/DxeLoadFunc.c
@@ -166,7 +166,7 @@ Create4GPageTablesIa32Pae (
// Protect the page table by marking the memory used for page table to be
// read-only.
//
- EnablePageTableProtection ((UINTN)PageMap, FALSE);
+ EnablePageTableProtection ((UINTN)PageMap, 3);
return (UINTN)PageMap;
}
diff --git a/MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.c b/MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.c
index df6196a41c..f02bdfe95d 100644
--- a/MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.c
+++ b/MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.c
@@ -485,14 +485,14 @@ Split1GPageTo2M (
@param[in] PageTableBase Base address of page table (CR3).
@param[in] Address Start address of a page to be set as read-only.
- @param[in] Level4Paging Level 4 paging flag.
+ @param[in] LevelofPaging Level of paging.
**/
VOID
SetPageTablePoolReadOnly (
IN UINTN PageTableBase,
IN EFI_PHYSICAL_ADDRESS Address,
- IN BOOLEAN Level4Paging
+ IN UINT8 LevelofPaging
)
{
UINTN Index;
@@ -502,9 +502,9 @@ SetPageTablePoolReadOnly (
UINT64 *PageTable;
UINT64 *NewPageTable;
UINT64 PageAttr;
- UINT64 LevelSize[5];
- UINT64 LevelMask[5];
- UINTN LevelShift[5];
+ UINT64 LevelSize[6];
+ UINT64 LevelMask[6];
+ UINTN LevelShift[6];
UINTN Level;
UINT64 PoolUnitSize;
@@ -521,23 +521,26 @@ SetPageTablePoolReadOnly (
LevelShift[2] = PAGING_L2_ADDRESS_SHIFT;
LevelShift[3] = PAGING_L3_ADDRESS_SHIFT;
LevelShift[4] = PAGING_L4_ADDRESS_SHIFT;
+ LevelShift[5] = PAGING_L5_ADDRESS_SHIFT;
LevelMask[1] = PAGING_4K_ADDRESS_MASK_64;
LevelMask[2] = PAGING_2M_ADDRESS_MASK_64;
LevelMask[3] = PAGING_1G_ADDRESS_MASK_64;
- LevelMask[4] = PAGING_1G_ADDRESS_MASK_64;
+ LevelMask[4] = PAGING_512G_ADDRESS_MASK_64;
+ LevelMask[5] = PAGING_256T_ADDRESS_MASK_64;
LevelSize[1] = SIZE_4KB;
LevelSize[2] = SIZE_2MB;
LevelSize[3] = SIZE_1GB;
LevelSize[4] = SIZE_512GB;
+ LevelSize[5] = SIZE_256TB;
AddressEncMask = PcdGet64 (PcdPteMemoryEncryptionAddressOrMask) &
PAGING_1G_ADDRESS_MASK_64;
PageTable = (UINT64 *)(UINTN)PageTableBase;
PoolUnitSize = PAGE_TABLE_POOL_UNIT_SIZE;
- for (Level = (Level4Paging) ? 4 : 3; Level > 0; --Level) {
+ for (Level = LevelofPaging; Level > 0; --Level) {
Index = ((UINTN)RShiftU64 (Address, LevelShift[Level]));
Index &= PAGING_PAE_INDEX_MASK;
@@ -607,13 +610,13 @@ SetPageTablePoolReadOnly (
Prevent the memory pages used for page table from been overwritten.
@param[in] PageTableBase Base address of page table (CR3).
- @param[in] Level4Paging Level 4 paging flag.
+ @param[in] LevelofPaging Level of paging.
**/
VOID
EnablePageTableProtection (
- IN UINTN PageTableBase,
- IN BOOLEAN Level4Paging
+ IN UINTN PageTableBase,
+ IN UINT8 LevelofPaging
)
{
PAGE_TABLE_POOL *HeadPool;
@@ -642,7 +645,7 @@ EnablePageTableProtection (
// protection to them one by one.
//
while (PoolSize > 0) {
- SetPageTablePoolReadOnly (PageTableBase, Address, Level4Paging);
+ SetPageTablePoolReadOnly (PageTableBase, Address, LevelofPaging);
Address += PAGE_TABLE_POOL_UNIT_SIZE;
PoolSize -= PAGE_TABLE_POOL_UNIT_SIZE;
}
@@ -696,7 +699,7 @@ CreateIdentityMappingPageTables (
UINTN TotalPagesNum;
UINTN BigPageAddress;
VOID *Hob;
- BOOLEAN Page5LevelEnabled;
+ UINT8 LevelofPaging;
BOOLEAN Page1GSupport;
PAGE_TABLE_1G_ENTRY *PageDirectory1GEntry;
UINT64 AddressEncMask;
@@ -743,16 +746,16 @@ CreateIdentityMappingPageTables (
//
// If cpu has already run in 64bit long mode PEI, Page table Level in DXE must align with previous level.
//
- Cr4.UintN = AsmReadCr4 ();
- Page5LevelEnabled = (Cr4.Bits.LA57 != 0);
- if (Page5LevelEnabled) {
+ Cr4.UintN = AsmReadCr4 ();
+ LevelofPaging = (Cr4.Bits.LA57 == 1) ? 5 : 4;
+ if (LevelofPaging == 5) {
ASSERT (PcdGetBool (PcdUse5LevelPageTable));
}
} else {
//
// If cpu runs in 32bit protected mode PEI, Page table Level in DXE is decided by PCD and feature capability.
//
- Page5LevelEnabled = FALSE;
+ LevelofPaging = 4;
if (PcdGetBool (PcdUse5LevelPageTable)) {
AsmCpuidEx (
CPUID_STRUCTURED_EXTENDED_FEATURE_FLAGS,
@@ -763,12 +766,12 @@ CreateIdentityMappingPageTables (
NULL
);
if (EcxFlags.Bits.FiveLevelPage != 0) {
- Page5LevelEnabled = TRUE;
+ LevelofPaging = 5;
}
}
}
- DEBUG ((DEBUG_INFO, "AddressBits=%u 5LevelPaging=%u 1GPage=%u\n", PhysicalAddressBits, Page5LevelEnabled, Page1GSupport));
+ DEBUG ((DEBUG_INFO, "AddressBits=%u LevelofPaging=%u 1GPage=%u\n", PhysicalAddressBits, LevelofPaging, Page1GSupport));
//
// IA-32e paging translates 48-bit linear addresses to 52-bit physical addresses
@@ -776,7 +779,7 @@ CreateIdentityMappingPageTables (
// due to either unsupported by HW, or disabled by PCD.
//
ASSERT (PhysicalAddressBits <= 52);
- if (!Page5LevelEnabled && (PhysicalAddressBits > 48)) {
+ if ((LevelofPaging != 5) && (PhysicalAddressBits > 48)) {
PhysicalAddressBits = 48;
}
@@ -811,7 +814,7 @@ CreateIdentityMappingPageTables (
//
// Substract the one page occupied by PML5 entries if 5-Level Paging is disabled.
//
- if (!Page5LevelEnabled) {
+ if (LevelofPaging != 5) {
TotalPagesNum--;
}
@@ -831,7 +834,7 @@ CreateIdentityMappingPageTables (
// By architecture only one PageMapLevel4 exists - so lets allocate storage for it.
//
PageMap = (VOID *)BigPageAddress;
- if (Page5LevelEnabled) {
+ if (LevelofPaging == 5) {
//
// By architecture only one PageMapLevel5 exists - so lets allocate storage for it.
//
@@ -853,7 +856,7 @@ CreateIdentityMappingPageTables (
PageMapLevel4Entry = (VOID *)BigPageAddress;
BigPageAddress += SIZE_4KB;
- if (Page5LevelEnabled) {
+ if (LevelofPaging == 5) {
//
// Make a PML5 Entry
//
@@ -947,7 +950,7 @@ CreateIdentityMappingPageTables (
ZeroMem (PageMapLevel4Entry, (512 - IndexOfPml4Entries) * sizeof (PAGE_MAP_AND_DIRECTORY_POINTER));
}
- if (Page5LevelEnabled) {
+ if (LevelofPaging == 5) {
Cr4.UintN = AsmReadCr4 ();
Cr4.Bits.LA57 = 1;
AsmWriteCr4 (Cr4.UintN);
@@ -961,7 +964,7 @@ CreateIdentityMappingPageTables (
// Protect the page table by marking the memory used for page table to be
// read-only.
//
- EnablePageTableProtection ((UINTN)PageMap, TRUE);
+ EnablePageTableProtection ((UINTN)PageMap, LevelofPaging);
//
// Set IA32_EFER.NXE if necessary.
diff --git a/MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.h b/MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.h
index 616ebe42b0..ac5eb7282d 100644
--- a/MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.h
+++ b/MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.h
@@ -149,14 +149,17 @@ typedef union {
#define PAGING_PAE_INDEX_MASK 0x1FF
-#define PAGING_4K_ADDRESS_MASK_64 0x000FFFFFFFFFF000ull
-#define PAGING_2M_ADDRESS_MASK_64 0x000FFFFFFFE00000ull
-#define PAGING_1G_ADDRESS_MASK_64 0x000FFFFFC0000000ull
+#define PAGING_4K_ADDRESS_MASK_64 0x000FFFFFFFFFF000ull
+#define PAGING_2M_ADDRESS_MASK_64 0x000FFFFFFFE00000ull
+#define PAGING_1G_ADDRESS_MASK_64 0x000FFFFFC0000000ull
+#define PAGING_512G_ADDRESS_MASK_64 0x000FF80000000000ull
+#define PAGING_256T_ADDRESS_MASK_64 0x000F800000000000ull
#define PAGING_L1_ADDRESS_SHIFT 12
#define PAGING_L2_ADDRESS_SHIFT 21
#define PAGING_L3_ADDRESS_SHIFT 30
#define PAGING_L4_ADDRESS_SHIFT 39
+#define PAGING_L5_ADDRESS_SHIFT 48
#define PAGING_PML4E_NUMBER 4
@@ -293,13 +296,13 @@ IsNullDetectionEnabled (
Prevent the memory pages used for page table from been overwritten.
@param[in] PageTableBase Base address of page table (CR3).
- @param[in] Level4Paging Level 4 paging flag.
+ @param[in] LevelofPaging Level of paging.
**/
VOID
EnablePageTableProtection (
- IN UINTN PageTableBase,
- IN BOOLEAN Level4Paging
+ IN UINTN PageTableBase,
+ IN UINT8 LevelofPaging
);
/**
diff --git a/UefiPayloadPkg/UefiPayloadEntry/Ia32/DxeLoadFunc.c b/UefiPayloadPkg/UefiPayloadEntry/Ia32/DxeLoadFunc.c
index cf9c03a9a8..6009232524 100644
--- a/UefiPayloadPkg/UefiPayloadEntry/Ia32/DxeLoadFunc.c
+++ b/UefiPayloadPkg/UefiPayloadEntry/Ia32/DxeLoadFunc.c
@@ -177,7 +177,7 @@ Create4GPageTablesIa32Pae (
// Protect the page table by marking the memory used for page table to be
// read-only.
//
- EnablePageTableProtection ((UINTN)PageMap, FALSE);
+ EnablePageTableProtection ((UINTN)PageMap, 3);
return (UINTN)PageMap;
}
diff --git a/UefiPayloadPkg/UefiPayloadEntry/X64/VirtualMemory.c b/UefiPayloadPkg/UefiPayloadEntry/X64/VirtualMemory.c
index 1899404b24..ad91ffbb4c 100644
--- a/UefiPayloadPkg/UefiPayloadEntry/X64/VirtualMemory.c
+++ b/UefiPayloadPkg/UefiPayloadEntry/X64/VirtualMemory.c
@@ -481,14 +481,14 @@ Split1GPageTo2M (
@param[in] PageTableBase Base address of page table (CR3).
@param[in] Address Start address of a page to be set as read-only.
- @param[in] Level4Paging Level 4 paging flag.
+ @param[in] LevelofPaging Level of paging.
**/
VOID
SetPageTablePoolReadOnly (
IN UINTN PageTableBase,
IN EFI_PHYSICAL_ADDRESS Address,
- IN BOOLEAN Level4Paging
+ IN UINT8 LevelofPaging
)
{
UINTN Index;
@@ -498,9 +498,9 @@ SetPageTablePoolReadOnly (
UINT64 *PageTable;
UINT64 *NewPageTable;
UINT64 PageAttr;
- UINT64 LevelSize[5];
- UINT64 LevelMask[5];
- UINTN LevelShift[5];
+ UINT64 LevelSize[6];
+ UINT64 LevelMask[6];
+ UINTN LevelShift[6];
UINTN Level;
UINT64 PoolUnitSize;
@@ -517,23 +517,26 @@ SetPageTablePoolReadOnly (
LevelShift[2] = PAGING_L2_ADDRESS_SHIFT;
LevelShift[3] = PAGING_L3_ADDRESS_SHIFT;
LevelShift[4] = PAGING_L4_ADDRESS_SHIFT;
+ LevelShift[5] = PAGING_L5_ADDRESS_SHIFT;
LevelMask[1] = PAGING_4K_ADDRESS_MASK_64;
LevelMask[2] = PAGING_2M_ADDRESS_MASK_64;
LevelMask[3] = PAGING_1G_ADDRESS_MASK_64;
- LevelMask[4] = PAGING_1G_ADDRESS_MASK_64;
+ LevelMask[4] = PAGING_512G_ADDRESS_MASK_64;
+ LevelMask[5] = PAGING_256T_ADDRESS_MASK_64;
LevelSize[1] = SIZE_4KB;
LevelSize[2] = SIZE_2MB;
LevelSize[3] = SIZE_1GB;
LevelSize[4] = SIZE_512GB;
+ LevelSize[5] = SIZE_256TB;
AddressEncMask = PcdGet64 (PcdPteMemoryEncryptionAddressOrMask) &
PAGING_1G_ADDRESS_MASK_64;
PageTable = (UINT64 *)(UINTN)PageTableBase;
PoolUnitSize = PAGE_TABLE_POOL_UNIT_SIZE;
- for (Level = (Level4Paging) ? 4 : 3; Level > 0; --Level) {
+ for (Level = LevelofPaging; Level > 0; --Level) {
Index = ((UINTN)RShiftU64 (Address, LevelShift[Level]));
Index &= PAGING_PAE_INDEX_MASK;
@@ -603,13 +606,13 @@ SetPageTablePoolReadOnly (
Prevent the memory pages used for page table from been overwritten.
@param[in] PageTableBase Base address of page table (CR3).
- @param[in] Level4Paging Level 4 paging flag.
+ @param[in] LevelofPaging Level of paging.
**/
VOID
EnablePageTableProtection (
- IN UINTN PageTableBase,
- IN BOOLEAN Level4Paging
+ IN UINTN PageTableBase,
+ IN UINT8 LevelofPaging
)
{
PAGE_TABLE_POOL *HeadPool;
@@ -638,7 +641,7 @@ EnablePageTableProtection (
// protection to them one by one.
//
while (PoolSize > 0) {
- SetPageTablePoolReadOnly (PageTableBase, Address, Level4Paging);
+ SetPageTablePoolReadOnly (PageTableBase, Address, LevelofPaging);
Address += PAGE_TABLE_POOL_UNIT_SIZE;
PoolSize -= PAGE_TABLE_POOL_UNIT_SIZE;
}
@@ -691,7 +694,7 @@ CreateIdentityMappingPageTables (
UINTN TotalPagesNum;
UINTN BigPageAddress;
VOID *Hob;
- BOOLEAN Enable5LevelPaging;
+ UINT8 LevelofPaging;
BOOLEAN Page1GSupport;
PAGE_TABLE_1G_ENTRY *PageDirectory1GEntry;
UINT64 AddressEncMask;
@@ -740,10 +743,10 @@ CreateIdentityMappingPageTables (
// below logic inherits the 5-level paging setting from bootloader in IA-32e mode
// and uses 4-level paging in legacy protected mode.
//
- Cr4.UintN = AsmReadCr4 ();
- Enable5LevelPaging = (BOOLEAN)(Cr4.Bits.LA57 == 1);
+ Cr4.UintN = AsmReadCr4 ();
+ LevelofPaging = (Cr4.Bits.LA57 == 1) ? 5 : 4;
- DEBUG ((DEBUG_INFO, "PayloadEntry: AddressBits=%u 5LevelPaging=%u 1GPage=%u\n", PhysicalAddressBits, Enable5LevelPaging, Page1GSupport));
+ DEBUG ((DEBUG_INFO, "PayloadEntry: AddressBits=%u LevelofPaging=%u 1GPage=%u\n", PhysicalAddressBits, LevelofPaging, Page1GSupport));
//
// IA-32e paging translates 48-bit linear addresses to 52-bit physical addresses
@@ -751,7 +754,7 @@ CreateIdentityMappingPageTables (
// due to either unsupported by HW, or disabled by PCD.
//
ASSERT (PhysicalAddressBits <= 52);
- if (!Enable5LevelPaging && (PhysicalAddressBits > 48)) {
+ if ((LevelofPaging != 5) && (PhysicalAddressBits > 48)) {
PhysicalAddressBits = 48;
}
@@ -786,7 +789,7 @@ CreateIdentityMappingPageTables (
//
// Substract the one page occupied by PML5 entries if 5-Level Paging is disabled.
//
- if (!Enable5LevelPaging) {
+ if (LevelofPaging != 5) {
TotalPagesNum--;
}
@@ -806,7 +809,7 @@ CreateIdentityMappingPageTables (
// By architecture only one PageMapLevel4 exists - so lets allocate storage for it.
//
PageMap = (VOID *)BigPageAddress;
- if (Enable5LevelPaging) {
+ if (LevelofPaging == 5) {
//
// By architecture only one PageMapLevel5 exists - so lets allocate storage for it.
//
@@ -828,7 +831,7 @@ CreateIdentityMappingPageTables (
PageMapLevel4Entry = (VOID *)BigPageAddress;
BigPageAddress += SIZE_4KB;
- if (Enable5LevelPaging) {
+ if (LevelofPaging == 5) {
//
// Make a PML5 Entry
//
@@ -922,7 +925,7 @@ CreateIdentityMappingPageTables (
ZeroMem (PageMapLevel4Entry, (512 - IndexOfPml4Entries) * sizeof (PAGE_MAP_AND_DIRECTORY_POINTER));
}
- if (Enable5LevelPaging) {
+ if (LevelofPaging == 5) {
//
// For the PML5 entries we are not using fill in a null entry.
//
@@ -933,7 +936,7 @@ CreateIdentityMappingPageTables (
// Protect the page table by marking the memory used for page table to be
// read-only.
//
- EnablePageTableProtection ((UINTN)PageMap, TRUE);
+ EnablePageTableProtection ((UINTN)PageMap, LevelofPaging);
//
// Set IA32_EFER.NXE if necessary.
diff --git a/UefiPayloadPkg/UefiPayloadEntry/X64/VirtualMemory.h b/UefiPayloadPkg/UefiPayloadEntry/X64/VirtualMemory.h
index 616ebe42b0..ac5eb7282d 100644
--- a/UefiPayloadPkg/UefiPayloadEntry/X64/VirtualMemory.h
+++ b/UefiPayloadPkg/UefiPayloadEntry/X64/VirtualMemory.h
@@ -149,14 +149,17 @@ typedef union {
#define PAGING_PAE_INDEX_MASK 0x1FF
-#define PAGING_4K_ADDRESS_MASK_64 0x000FFFFFFFFFF000ull
-#define PAGING_2M_ADDRESS_MASK_64 0x000FFFFFFFE00000ull
-#define PAGING_1G_ADDRESS_MASK_64 0x000FFFFFC0000000ull
+#define PAGING_4K_ADDRESS_MASK_64 0x000FFFFFFFFFF000ull
+#define PAGING_2M_ADDRESS_MASK_64 0x000FFFFFFFE00000ull
+#define PAGING_1G_ADDRESS_MASK_64 0x000FFFFFC0000000ull
+#define PAGING_512G_ADDRESS_MASK_64 0x000FF80000000000ull
+#define PAGING_256T_ADDRESS_MASK_64 0x000F800000000000ull
#define PAGING_L1_ADDRESS_SHIFT 12
#define PAGING_L2_ADDRESS_SHIFT 21
#define PAGING_L3_ADDRESS_SHIFT 30
#define PAGING_L4_ADDRESS_SHIFT 39
+#define PAGING_L5_ADDRESS_SHIFT 48
#define PAGING_PML4E_NUMBER 4
@@ -293,13 +296,13 @@ IsNullDetectionEnabled (
Prevent the memory pages used for page table from been overwritten.
@param[in] PageTableBase Base address of page table (CR3).
- @param[in] Level4Paging Level 4 paging flag.
+ @param[in] LevelofPaging Level of paging.
**/
VOID
EnablePageTableProtection (
- IN UINTN PageTableBase,
- IN BOOLEAN Level4Paging
+ IN UINTN PageTableBase,
+ IN UINT8 LevelofPaging
);
/**
--
2.25.1
-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#120771): https://edk2.groups.io/g/devel/message/120771
Mute This Topic: https://groups.io/mt/109530632/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-
next reply other threads:[~2024-11-12 5:59 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-11-12 5:59 Ning Feng via groups.io [this message]
-- strict thread matches above, loose matches on Subject: below --
2024-11-12 6:14 [edk2-devel] [PATCH] MedModulePkg/DxeIplPeim: Fix pagetable protection region in 5 level paging Ning Feng via groups.io
2024-11-12 6:06 Ning Feng via groups.io
2024-11-12 6:03 Ning Feng via groups.io
2024-11-12 5:50 Ning Feng via groups.io
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-list from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20241112055944.1820285-1-ning.feng@intel.com \
--to=devel@edk2.groups.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox