L1TFv4 5
Subsystems (10)
PATCH v5 4/8 L1TFv4 8
In reply to Andi KleenThe L1TF workaround doesn't make any attempt to mitigate speculate accesses to the first physical page for zeroed PTEs. Normally it only contains some data from the early real mode BIOS.
I couldn't convince myself we always reserve the first page in all configurations, so add an extra reservation call to make sure it is really reserved. In most configurations (e.g. with the standard reservations) it's likely a nop.
--- arch/x86/kernel/setup.c | 3 +++ 1 file changed, 3 insertions(+)
arch/x86/kernel/setup.c +3 -0
@@ -823,6 +823,9 @@ void __init setup_arch(char **cmdline_p)memblock_reserve (__pa_symbol (_text), (unsigned long )__bss_stop - (unsigned long )_text); +/* Make sure page 0 is always reserved */ +memblock_reserve (0 ,PAGE_SIZE ); +early_reserve_initrd ();/*
Signature · 1 line
2.14.3
v5 cover L1TFv4 5
Updated version of the L1TF patchkit for the native OS.
Addressed Thomas' earlier review comments and some minor improvements. For details see the individual patches.
Andi Kleen (7):
x86, l1tf: Increase 32bit PAE __PHYSICAL_PAGE_MASK x86, l1tf: Protect PROT_NONE PTEs against speculation x86, l1tf: Make sure the first page is always reserved x86, l1tf: Add sysfs reporting for l1tf
x86, l1tf: Report if too much memory for L1TF workaround x86, l1tf: Limit swap file size to MAX_PA/2
mm, l1tf: Disallow non privileged high MMIO PROT_NONE mappings
Linus Torvalds (1):
x86, l1tf: Protect swap entries against L1TF
arch/x86/include/asm/cpufeatures.h | 2 ++ arch/x86/include/asm/page_32_types.h | 9 +++++-- arch/x86/include/asm/pgtable-2level.h | 17 ++++++++++++ arch/x86/include/asm/pgtable-3level.h | 2 ++ arch/x86/include/asm/pgtable-invert.h | 32 +++++++++++++++++++++++ arch/x86/include/asm/pgtable.h | 48 ++++++++++++++++++++++++---------- arch/x86/include/asm/pgtable_64.h | 38 +++++++++++++++++++-------- arch/x86/include/asm/processor.h | 5 ++++ arch/x86/kernel/cpu/bugs.c | 11 ++++++++ arch/x86/kernel/cpu/common.c | 30 +++++++++++++++++++++ arch/x86/kernel/cpu/cpuid-deps.c | 1 + arch/x86/kernel/setup.c | 26 ++++++++++++++++++- arch/x86/mm/init.c | 15 +++++++++++ arch/x86/mm/mmap.c | 21 +++++++++++++++ drivers/base/cpu.c | 8 ++++++ include/asm-generic/pgtable.h | 12 +++++++++ include/linux/cpu.h | 2 ++ include/linux/swapfile.h | 2 ++ mm/memory.c | 37 +++++++++++++++++++------- mm/mprotect.c | 49 +++++++++++++++++++++++++++++++++++ mm/swapfile.c | 46 ++++++++++++++++++++------------ 21 files changed, 360 insertions(+), 53 deletions(-) create mode 100644 arch/x86/include/asm/pgtable-invert.h
Signature · 1 line
2.14.3
PATCH v5 1/8 L1TFv4 6
On 32bit PAE the max PTE mask is currently set to 44 bit because that is the limit imposed by 32bit unsigned long PFNs in the VMs.
The L1TF PROT_NONE protection code uses the PTE masks to determine what bits to invert to make sure the higher bits are set for unmapped entries to prevent L1TF speculation attacks against EPT inside guests.
But our inverted mask has to match the host, and the host is likely 64bit and may use more than 43 bits of memory. We want to set all possible bits to be safe here.
So increase the mask on 32bit PAE to 52 to match 64bit.
The real limit is still 44 bits.
All Linux PTEs are created from unsigned long PFNs, so cannot be higher than 44 bits on a 32bit kernel. So these extra PFN bits should be never set. The only users of this macro are using it to look at PTEs, so it's safe.
--- arch/x86/include/asm/page_32_types.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
arch/x86/include/asm/page_32_types.h +7 -2
@@ -29,8 +29,13 @@#define N_EXCEPTION_STACKS 1#ifdef CONFIG_X86_PAE -/* 44=32+12, the limit we can fit into an unsigned long pfn */ -#define __PHYSICAL_MASK_SHIFT 44 +/* +* This is beyond the 44 bit limit imposed by the 32bit long pfns, +* but we need the full mask to make sure inverted PROT_NONE +* entries have all the host bits set in a guest. +* The real limit is still 44 bits. +*/ +#define __PHYSICAL_MASK_SHIFT 52#define __VIRTUAL_MASK_SHIFT 32#else /* !CONFIG_X86_PAE */
Signature · 1 line
2.14.3
PATCH v5 2/8 L1TFv4 7
In reply to Andi KleenWith L1 terminal fault the CPU speculates into unmapped PTEs, and resulting side effects allow to read the memory the PTE is pointing too, if its values are still in the L1 cache.
For swapped out pages Linux uses unmapped PTEs and stores a swap entry into them.
We need to make sure the swap entry is not pointing to valid memory, which requires setting higher bits (between bit 36 and bit 45) that are inside the CPUs physical address space, but outside any real memory.
To do this we invert the offset to make sure the higher bits are always set, as long as the swap file is not too big.
Here's a patch that switches the order of "type" and
"offset" in the x86-64 encoding, in addition to doing the binary 'not' on the offset.
That means that now the offset is bits 9-58 in the page table, and that the type is in the bits that hardware generally doesn't care about.
That, in turn, means that if you have a desktop chip with only 40 bits of physical addressing, now that the offset starts at bit 9, you still have to have 30 bits of offset actually *in use* until bit 39 ends up being clear.
So that's 4 terabyte of swap space (because the offset is counted in pages, so 30 bits of offset is 42 bits of actual coverage). With bigger physical addressing, that obviously grows further, until you hit the limit of the offset (at 50 bits of offset - 62 bits of actual swap file coverage).
Note there is no workaround for 32bit !PAE, or on systems which have more than MAX_PA/2 memory. The later case is very unlikely to happen on real systems.
[updated description and minor tweaks by AK]
--- arch/x86/include/asm/pgtable_64.h | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-)
arch/x86/include/asm/pgtable_64.h +25 -11
@@ -273,7 +273,7 @@ static inline int pgd_large(pgd_t pgd) { return 0; }* * | ... | 11| 10| 9|8|7|6|5| 4| 3|2| 1|0| <- bit number * | ... |SW3|SW2|SW1|G|L|D|A|CD|WT|U| W|P| <- bit names -* | OFFSET (14->63) | TYPE (9-13) |0|0|X|X| X| X|X|SD|0| <- swp entry +* | TYPE (59-63) | ~OFFSET (9-58) |0|0|X|X| X| X|X|SD|0| <- swp entry * * G (8) is aliased and used as a PROT_NONE indicator for * !present ptes. We need to start storing swap entries above
@@ -286,20 +286,34 @@ static inline int pgd_large(pgd_t pgd) { return 0; }* * Bit 7 in swp entry should be 0 because pmd_present checks not only P, * but also L and G. +* +* The offset is inverted by a binary not operation to make the high +* physical bits set. */ -#define SWP_TYPE_FIRST_BIT (_PAGE_BIT_PROTNONE + 1) -#define SWP_TYPE_BITS 5 -/* Place the offset above the type: */ -#define SWP_OFFSET_FIRST_BIT (SWP_TYPE_FIRST_BIT + SWP_TYPE_BITS) +#define SWP_TYPE_BITS 5 + +#define SWP_OFFSET_FIRST_BIT (_PAGE_BIT_PROTNONE + 1) + +/* We always extract/encode the offset by shifting it all the way up, and then down again */ +#define SWP_OFFSET_SHIFT (SWP_OFFSET_FIRST_BIT+SWP_TYPE_BITS)#define MAX_SWAPFILES_CHECK () BUILD_BUG_ON(MAX_SWAPFILES_SHIFT > SWP_TYPE_BITS) -#define __swp_type (x) (((x).val >> (SWP_TYPE_FIRST_BIT)) \ - & ((1U << SWP_TYPE_BITS) - 1)) -#define __swp_offset (x) ((x).val >> SWP_OFFSET_FIRST_BIT) -#define __swp_entry (type, offset) ((swp_entry_t) { \ - ((type) << (SWP_TYPE_FIRST_BIT)) \ - | ((offset) << SWP_OFFSET_FIRST_BIT) }) +/* Extract the high bits for type */ +#define __swp_type (x) ((x).val >> (64 - SWP_TYPE_BITS)) + +/* Shift up (to get rid of type), then down to get value */ +#define __swp_offset (x) (~(x).val << SWP_TYPE_BITS >> SWP_OFFSET_SHIFT) + +/* +* Shift the offset up "too far" by TYPE bits, then down again +* The offset is inverted by a binary not operation to make the high +* physical bits set. +*/ +#define __swp_entry (type, offset) ((swp_entry_t) { \ + (~(unsigned long)(offset) << SWP_OFFSET_SHIFT >> SWP_TYPE_BITS) \ + | ((unsigned long)(type) << (64-SWP_TYPE_BITS)) }) +#define __pte_to_swp_entry (pte) ((swp_entry_t) { pte_val((pte)) })#define __pmd_to_swp_entry (pmd) ((swp_entry_t) { pmd_val((pmd)) })#define __swp_entry_to_pte (x) ((pte_t) { .pte = (x).val })
Signature · 1 line
2.14.3
PATCH v5 5/8 L1TFv4 0
In reply to Andi KleenL1TF core kernel workarounds are cheap and normally always enabled, However we still want to report in sysfs if the system is vulnerable or mitigated. Add the necessary checks.
- We use the same checks as Meltdown to determine if the system is vulnerable. This excludes some Atom CPUs which don't have this problem.
- We check for the (very unlikely) memory > MAX_PA/2 case
- We check for 32bit non PAE and warn
Note this patch will likely conflict with some other workaround patches floating around, but should be straight forward to fix.
v2: Use positive instead of negative flag for WA. Fix override reporting.
v3: Fix L1TF_WA flag settting
v4: Rebase to SSB tree
v5: Minor cleanups. No functional changes.
Don't mark atoms and knights as vulnerable
--- arch/x86/include/asm/cpufeatures.h | 2 ++ arch/x86/kernel/cpu/bugs.c | 11 +++++++++++ arch/x86/kernel/cpu/common.c | 30 ++++++++++++++++++++++++++++++ drivers/base/cpu.c | 8 ++++++++ include/linux/cpu.h | 2 ++ 5 files changed, 53 insertions(+)
arch/x86/include/asm/cpufeatures.h +2 -0
@@ -219,6 +219,7 @@#define X86_FEATURE_IBPB ( 7*32+26)/* Indirect Branch Prediction Barrier */ #define X86_FEATURE_STIBP ( 7*32+27)/* Single Thread Indirect Branch Predictors */ #define X86_FEATURE_ZEN ( 7*32+28)/* "" CPU is AMD family 0x17 (Zen) */ +#define X86_FEATURE_L1TF_WA ( 7*32+29)/* "" L1TF workaround used */ /* Virtualization flags: Linux defined, word 8 */ #define X86_FEATURE_TPR_SHADOW ( 8*32+ 0)/* Intel TPR Shadow */
@@ -371,5 +372,6 @@#define X86_BUG_SPECTRE_V1 X86_BUG(15)/* CPU is affected by Spectre variant 1 attack with conditional branches */ #define X86_BUG_SPECTRE_V2 X86_BUG(16)/* CPU is affected by Spectre variant 2 attack with indirect branches */ #define X86_BUG_SPEC_STORE_BYPASS X86_BUG(17)/* CPU is affected by speculative store bypass attack */ +#define X86_BUG_L1TF X86_BUG(18)/* CPU is affected by L1 Terminal Fault */ #endif /* _ASM_X86_CPUFEATURES_H */
arch/x86/kernel/cpu/bugs.c +11 -0
@@ -707,4 +707,15 @@ ssize_t cpu_show_spec_store_bypass(struct device *dev, struct device_attribute * {return cpu_show_common (dev, attr, buf,X86_BUG_SPEC_STORE_BYPASS ); } + +ssize_t cpu_show_l1tf (struct device *dev,struct device_attribute *attr,char *buf) +{ +if (!boot_cpu_has_bug (X86_BUG_L1TF )) +return sprintf (buf,"Not affected\n" ); + +if (boot_cpu_has (X86_FEATURE_L1TF_WA )) +return sprintf (buf,"Mitigated\n" ); + +return sprintf (buf,"Vulnerable\n" ); +}#endif
arch/x86/kernel/cpu/common.c +30 -0
@@ -924,6 +924,15 @@ static void identify_cpu_without_cpuid(struct cpuinfo_x86 *c)#endif } +static void __init l1tf_init_workaround (void ) +{ +#if CONFIG_PGTABLE_LEVELS ==2 +pr_warn ("Kernel not compiled for PAE. No workaround for L1TF\n" ); +#else +setup_force_cpu_cap (X86_FEATURE_L1TF_WA ); +#endif +} +static const __initconst struct x86_cpu_id cpu_no_speculation[] = { {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_CEDARVIEW ,X86_FEATURE_ANY }, {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_CLOVERVIEW ,X86_FEATURE_ANY },
@@ -966,6 +975,21 @@ static const __initconst struct x86_cpu_id cpu_no_spec_store_bypass[] = { {} }; +static const __initconst struct x86_cpu_id cpu_no_l1tf[] = { +/* in addition to cpu_no_speculation */ + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_SILVERMONT1 }, + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_SILVERMONT2 }, + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_AIRMONT }, + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_MERRIFIELD }, + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_MOOREFIELD }, + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_GOLDMONT }, + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_DENVERTON }, + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_ATOM_GEMINI_LAKE }, + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_XEON_PHI_KNL }, + {X86_VENDOR_INTEL ,6 ,INTEL_FAM6_XEON_PHI_KNM }, + {} +}; +static void __init cpu_set_bug_bits (struct cpuinfo_x86 *c) {u64 ia32_cap =0 ;
@@ -991,6 +1015,12 @@ static void __init cpu_set_bug_bits(struct cpuinfo_x86 *c)return ;setup_force_cpu_bug (X86_BUG_CPU_MELTDOWN ); + +if (x86_match_cpu (cpu_no_l1tf)) +return ; + +setup_force_cpu_bug (X86_BUG_L1TF ); +l1tf_init_workaround (); }/*
drivers/base/cpu.c +8 -0
@@ -540,16 +540,24 @@ ssize_t __weak cpu_show_spec_store_bypass(struct device *dev,return sprintf (buf,"Not affected\n" ); } +ssize_t __weak cpu_show_l1tf (struct device *dev, +struct device_attribute *attr,char *buf) +{ +return sprintf (buf,"Not affected\n" ); +} + static DEVICE_ATTR(meltdown, 0444, cpu_show_meltdown, NULL); static DEVICE_ATTR(spectre_v1, 0444, cpu_show_spectre_v1, NULL); static DEVICE_ATTR(spectre_v2, 0444, cpu_show_spectre_v2, NULL); static DEVICE_ATTR(spec_store_bypass, 0444, cpu_show_spec_store_bypass, NULL); +static DEVICE_ATTR(l1tf, 0444, cpu_show_l1tf, NULL);static struct attribute *cpu_root_vulnerabilities_attrs[] = { &dev_attr_meltdown.attr, &dev_attr_spectre_v1.attr, &dev_attr_spectre_v2.attr, &dev_attr_spec_store_bypass.attr, + &dev_attr_l1tf.attr,NULL };
include/linux/cpu.h +2 -0
@@ -55,6 +55,8 @@ extern ssize_t cpu_show_spectre_v2(struct device *dev, struct device_attribute *attr, char *buf);extern ssize_t cpu_show_spec_store_bypass (struct device *dev,struct device_attribute *attr,char *buf); +extern ssize_t cpu_show_l1tf (struct device *dev, +struct device_attribute *attr,char *buf); extern __printf(4, 5) struct device *cpu_device_create(struct device *parent, void *drvdata,
Signature · 1 line
2.14.3
PATCH v5 6/8 L1TFv4 4
In reply to Andi KleenIf the system has more than MAX_PA/2 physical memory the invert page workarounds don't protect the system against the L1TF attack anymore, because an inverted physical address will point to valid memory.
We cannot do much here, after all users want to use the memory, but at least print a warning and report the system as vulnerable in sysfs
Note this is all extremely unlikely to happen on a real machine because they typically have far more MAX_PA than DIMM slots
Some VMs also report fairly small PAs to guest, e.g. only 36bits. In this case the threshold will be lower, but applies only to the maximum guest size.
Since this needs to clear a feature bit that has been forced earlier add a special "unforce" macro that supports this.
squash! x86, l1tf: Report if too much memory for L1TF workaround
v2:
Do force clearing in setup_clear_... Rename variables, fix comments, change formatting. Use l1tf_pfn_limit()
--- arch/x86/kernel/cpu/cpuid-deps.c | 1 + arch/x86/kernel/setup.c | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-)
arch/x86/kernel/cpu/cpuid-deps.c +1 -0
@@ -118,4 +118,5 @@ void clear_cpu_cap(struct cpuinfo_x86 *c, unsigned int feature)void setup_clear_cpu_cap (unsigned int feature) {do_clear_cpu_cap (NULL , feature); +clear_bit (feature, (unsigned long *)cpu_caps_set); }
arch/x86/kernel/setup.c +22 -1
@@ -785,7 +785,26 @@ static void __init trim_low_memory_range(void) {memblock_reserve (0 ,ALIGN (reserve_low,PAGE_SIZE )); } - + +static __init void check_maxpa_memory (void ) +{ +u64 half_pa; + +if (!boot_cpu_has (X86_BUG_L1TF )) +return ; + + half_pa = (u64 )l1tf_pfn_limit () <<PAGE_SHIFT ; + +/* +* This is extremely unlikely to happen because almost all systems have far +* more MAX_PA/2 than DIMM slots. +*/ +if (e820__mapped_any (half_pa,ULLONG_MAX - half_pa,E820_TYPE_RAM )) { +pr_warn ("System has more than MAX_PA/2 memory. Disabled L1TF workaround\n" ); +setup_clear_cpu_cap (X86_FEATURE_L1TF_WA ); + } +} +/* * Dump out kernel offset information on panic. */
@@ -1022,6 +1041,8 @@ void __init setup_arch(char **cmdline_p)insert_resource (&iomem_resource, &data_resource);insert_resource (&iomem_resource, &bss_resource); +check_maxpa_memory (); +e820_add_kernel_range ();trim_bios_range ();#ifdef CONFIG_X86_32
Signature · 1 line
2.14.3
PATCH v5 7/8 L1TFv4 3
In reply to Andi KleenFor the L1TF workaround we want to limit the swap file size to below MAX_PA/2, so that the higher bits of the swap offset inverted never point to valid memory.
Add a way for the architecture to override the swap file size check in swapfile.c and add a x86 specific max swapfile check function that enforces that limit.
The check is only enabled if the CPU is vulnerable to L1TF.
In VMs with 42bit MAX_PA the typical limit is 2TB now, on a native system with 46bit PA it is 32TB. The limit is only per individual swap file, so it's always possible to exceed these limits with multiple swap files or partitions.
v2: Use new helper for maxpa_mask computation.
v3: Use l1tf_pfn_limit (Thomas)
Reformat comment
--- arch/x86/include/asm/processor.h | 5 +++++ arch/x86/mm/init.c | 15 +++++++++++++ include/linux/swapfile.h | 2 ++ mm/swapfile.c | 46 ++++++++++++++++++++++++++-------------- 4 files changed, 52 insertions(+), 16 deletions(-)
arch/x86/include/asm/processor.h +5 -0
@@ -181,6 +181,11 @@ extern const struct seq_operations cpuinfo_op;extern void cpu_detect (struct cpuinfo_x86 *c); +static inline unsigned long l1tf_pfn_limit (void ) +{ +return BIT (boot_cpu_data.x86_phys_bits -1 -PAGE_SHIFT ) -1 ; +} +extern void early_cpu_init (void );extern void identify_boot_cpu (void );extern void identify_secondary_cpu (struct cpuinfo_x86 *);
arch/x86/mm/init.c +15 -0
@@ -4,6 +4,8 @@#include <linux/swap.h> #include <linux/memblock.h> #include <linux/bootmem.h> /* for max_low_pfn */ +#include <linux/swapfile.h> +#include <linux/swapops.h> #include <asm/set_memory.h> #include <asm/e820/api.h>
@@ -878,3 +880,16 @@ void update_cache_mode_entry(unsigned entry, enum page_cache_mode cache) __cachemode2pte_tbl[cache] =__cm_idx2pte (entry); __pte2cachemode_tbl[entry] = cache; } + +unsigned long max_swapfile_size (void ) +{ +unsigned long pages; + + pages =generic_max_swapfile_size (); + +if (boot_cpu_has (X86_BUG_L1TF )) { +/* Limit the swap file size to MAX_PA/2 for L1TF workaround */ + pages = min_t(unsigned long, l1tf_pfn_limit() + 1, pages); + } +return pages; +}
include/linux/swapfile.h +2 -0
@@ -10,5 +10,7 @@ extern spinlock_t swap_lock;extern struct plist_head swap_active_head;extern struct swap_info_struct *swap_info[];extern int try_to_unuse (unsigned int ,bool ,unsigned long ); +extern unsigned long generic_max_swapfile_size (void ); +extern unsigned long max_swapfile_size (void );#endif /* _LINUX_SWAPFILE_H */
mm/swapfile.c +30 -16
58 lines not on this page · raw message
Signature · 1 line
2.14.3
PATCH v5 3/8 L1TFv4 2
In reply to Andi KleenWe also need to protect PTEs that are set to PROT_NONE against L1TF speculation attacks.
This is important inside guests, because L1TF speculation bypasses physical page remapping. While the VM has its own migitations preventing leaking data from other VMs into the guest, this would still risk leaking the wrong page inside the current guest.
This uses the same technique as Linus' swap entry patch:
while an entry is is in PROTNONE state we invert the complete PFN part part of it. This ensures that the the highest bit will point to non existing memory.
The invert is done by pte/pmd_modify and pfn/pmd/pud_pte for PROTNONE and pte/pmd/pud_pfn undo it.
We assume that noone tries to touch the PFN part of a PTE without using these primitives.
This doesn't handle the case that MMIO is on the top of the CPU physical memory. If such an MMIO region was exposed by an unpriviledged driver for mmap it would be possible to attack some real memory. However this situation is all rather unlikely.
For 32bit non PAE we don't try inversion because there are really not enough bits to protect anything.
Q: Why does the guest need to be protected when the HyperVisor already has L1TF mitigations?
A: Here's an example:
You have physical pages 1 2. They get mapped into a guest as GPA 1 -> PA 2
GPA 2 -> PA 1
through EPT.
The L1TF speculation ignores the EPT remapping.
Now the guest kernel maps GPA 1 to process A and GPA 2 to process B, and they belong to different users and should be isolated.
A sets the GPA 1 PA 2 PTE to PROT_NONE to bypass the EPT remapping and gets read access to the underlying physical page. Which in this case points to PA 2, so it can read process B's data, if it happened to be in L1.
So we broke isolation inside the guest.
There's nothing the hypervisor can do about this. This mitigation has to be done in the guest.
v2: Use new helper to generate XOR mask to invert (Linus)
v3: Use inline helper for protnone mask checking
v4: Use inline helpers to check for PROT_NONE changes
--- arch/x86/include/asm/pgtable-2level.h | 17 ++++++++++++++ arch/x86/include/asm/pgtable-3level.h | 2 ++ arch/x86/include/asm/pgtable-invert.h | 32 +++++++++++++++++++++++++ arch/x86/include/asm/pgtable.h | 44 ++++++++++++++++++++++++----------- arch/x86/include/asm/pgtable_64.h | 2 ++ 5 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 arch/x86/include/asm/pgtable-invert.h
arch/x86/include/asm/pgtable-2level.h +17 -0
@@ -95,4 +95,21 @@ static inline unsigned long pte_bitop(unsigned long value, unsigned int rightshi#define __pte_to_swp_entry (pte) ((swp_entry_t) { (pte).pte_low })#define __swp_entry_to_pte (x) ((pte_t) { .pte = (x).val }) +/* No inverted PFNs on 2 level page tables */ + +static inline u64 protnone_mask (u64 val) +{ +return 0 ; +} + +static inline u64 flip_protnone_guard (u64 oldval,u64 val,u64 mask) +{ +return val; +} + +static inline bool __pte_needs_invert (u64 val) +{ +return false; +} +#endif /* _ASM_X86_PGTABLE_2LEVEL_H */
arch/x86/include/asm/pgtable-3level.h +2 -0
@@ -295,4 +295,6 @@ static inline pte_t gup_get_pte(pte_t *ptep) return pte; } +#include <asm/pgtable-invert.h> +#endif /* _ASM_X86_PGTABLE_3LEVEL_H */
arch/x86/include/asm/pgtable-invert.h new file +32 -0
@@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_PGTABLE_INVERT_H +#define _ASM_PGTABLE_INVERT_H 1 + +#ifndef __ASSEMBLY__ + +static inline bool __pte_needs_invert (u64 val) +{ +return (val & (_PAGE_PRESENT|_PAGE_PROTNONE)) == _PAGE_PROTNONE; +} + +/* Get a mask to xor with the page table entry to get the correct pfn. */ +static inline u64 protnone_mask (u64 val) +{ +return __pte_needs_invert (val) ? ~0ull :0 ; +} + +static inline u64 flip_protnone_guard (u64 oldval,u64 val,u64 mask) +{ +/* +* When a PTE transitions from NONE to !NONE or vice-versa +* invert the PFN part to stop speculation. +* pte_pfn undoes this when needed. +*/ +if (__pte_needs_invert (oldval) !=__pte_needs_invert (val)) + val = (val & ~mask) | (~val & mask); +return val; +} + +#endif /* __ASSEMBLY__ */ + +#endif
arch/x86/include/asm/pgtable.h +31 -13
92 lines not on this page · raw message
arch/x86/include/asm/pgtable_64.h +2 -0
@@ -357,5 +357,7 @@ static inline bool gup_fast_permitted(unsigned long start, int nr_pages,return true; } +#include <asm/pgtable-invert.h> +#endif /* !__ASSEMBLY__ */ #endif /* _ASM_X86_PGTABLE_64_H */
Signature · 1 line
2.14.3
PATCH v5 8/8 L1TFv4 1
In reply to Andi KleenFor L1TF PROT_NONE mappings are protected by inverting the PFN in the page table entry. This sets the high bits in the CPU's address space, thus making sure to point to not point an unmapped entry to valid cached memory.
Some server system BIOS put the MMIO mappings high up in the physical address space. If such an high mapping was mapped to an unprivileged user they could attack low memory by setting such a mapping to PROT_NONE. This could happen through a special device driver which is not access protected. Normal /dev/mem is of course access protect.
To avoid this we forbid PROT_NONE mappings or mprotect for high MMIO mappings.
Valid page mappings are allowed because the system is then unsafe anyways.
We don't expect users to commonly use PROT_NONE on MMIO. But to minimize any impact here we only do this if the mapping actually refers to a high MMIO address (defined as the MAX_PA-1 bit being set), and also skip the check for root.
For mmaps this is straight forward and can be handled in vm_insert_pfn and in remap_pfn_range().
For mprotect it's a bit trickier. At the point we're looking at the actual PTEs a lot of state has been changed and would be difficult to undo on an error. Since this is a uncommon case we use a separate early page talk walk pass for MMIO PROT_NONE mappings that checks for this condition early. For non MMIO and non PROT_NONE there are no changes.
v2: Use new helpers added earlier
v3: Fix inverted check added in v3
v4: Use l1tf_pfn_limit (Thomas)
Add comment for locked down kernels
--- arch/x86/include/asm/pgtable.h | 4 ++++ arch/x86/mm/mmap.c | 21 ++++++++++++++++++ include/asm-generic/pgtable.h | 12 +++++++++++ mm/memory.c | 37 ++++++++++++++++++++++--------- mm/mprotect.c | 49 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 10 deletions(-)
arch/x86/include/asm/pgtable.h +4 -0
@@ -1338,6 +1338,10 @@ static inline bool pud_access_permitted(pud_t pud, bool write) return __pte_access_permitted(pud_val(pud), write); } +#define __HAVE_ARCH_PFN_MODIFY_ALLOWED 1 +extern bool pfn_modify_allowed (unsigned long pfn,pgprot_t prot); +static inline bool arch_has_pfn_modify_check (void ) {return true; } +#include <asm-generic/pgtable.h> #endif /* __ASSEMBLY__ */
arch/x86/mm/mmap.c +21 -0
@@ -240,3 +240,24 @@ int valid_mmap_phys_addr_range(unsigned long pfn, size_t count)return phys_addr_valid (addr + count -1 ); } + +/* +* Only allow root to set high MMIO mappings to PROT_NONE. +* This prevents an unpriv. user to set them to PROT_NONE and invert +* them, then pointing to valid memory for L1TF speculation. +* +* Note: for locked down kernels may want to disable the root override. +*/ +bool pfn_modify_allowed (unsigned long pfn,pgprot_t prot) +{ +if (!boot_cpu_has (X86_BUG_L1TF )) +return true; +if (!__pte_needs_invert (pgprot_val (prot))) +return true; +/* If it's real memory always allow */ +if (pfn_valid (pfn)) +return true; +if (pfn >l1tf_pfn_limit () && !capable (CAP_SYS_ADMIN )) +return false; +return true; +}
include/asm-generic/pgtable.h +12 -0
@@ -1097,4 +1097,16 @@ static inline void init_espfix_bsp(void) { }#endif #endif +#ifndef __HAVE_ARCH_PFN_MODIFY_ALLOWED +static inline bool pfn_modify_allowed (unsigned long pfn,pgprot_t prot) +{ +return true; +} + +static inline bool arch_has_pfn_modify_check (void ) +{ +return false; +} +#endif +#endif /* _ASM_GENERIC_PGTABLE_H */
mm/memory.c +27 -10
@@ -1891,6 +1891,9 @@ int vm_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr,if (addr < vma->vm_start || addr >= vma->vm_end)return -EFAULT ; +if (!pfn_modify_allowed (pfn, pgprot)) +return -EACCES ; +track_pfn_insert (vma, &pgprot,__pfn_to_pfn_t (pfn,PFN_DEV )); ret = insert_pfn(vma, addr, __pfn_to_pfn_t(pfn, PFN_DEV), pgprot,
@@ -1926,6 +1929,9 @@ static int __vm_insert_mixed(struct vm_area_struct *vma, unsigned long addr,track_pfn_insert (vma, &pgprot, pfn); +if (!pfn_modify_allowed (pfn_t_to_pfn (pfn), pgprot)) +return -EACCES ; +/* * If we don't have pte special, then we have to use the pfn_valid() * based VM_MIXEDMAP scheme (see vm_normal_page), and thus we *must*
@@ -1973,6 +1979,7 @@ static int remap_pte_range(struct mm_struct *mm, pmd_t *pmd, {pte_t *pte;spinlock_t *ptl; +int err =0 ; pte =pte_alloc_map_lock (mm, pmd, addr, &ptl);if (!pte)
@@ -1980,12 +1987,16 @@ static int remap_pte_range(struct mm_struct *mm, pmd_t *pmd,arch_enter_lazy_mmu_mode ();do {BUG_ON (!pte_none (*pte)); +if (!pfn_modify_allowed (pfn, prot)) { + err = -EACCES ; +break ; + }set_pte_at (mm, addr, pte,pte_mkspecial (pfn_pte (pfn, prot))); pfn++; }while (pte++, addr +=PAGE_SIZE , addr != end);arch_leave_lazy_mmu_mode ();pte_unmap_unlock (pte -1 , ptl); -return 0 ; +return err; } static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud,
@@ -1994,6 +2005,7 @@ static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud, {pmd_t *pmd;unsigned long next; +int err; pfn -= addr >>PAGE_SHIFT ; pmd =pmd_alloc (mm, pud, addr);
@@ -2002,9 +2014,10 @@ static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud,VM_BUG_ON (pmd_trans_huge (*pmd));do { next =pmd_addr_end (addr, end); -if (remap_pte_range (mm, pmd, addr, next, - pfn + (addr >>PAGE_SHIFT ), prot)) -return -ENOMEM ; + err =remap_pte_range (mm, pmd, addr, next, + pfn + (addr >>PAGE_SHIFT ), prot); +if (err) +return err; }while (pmd++, addr = next, addr != end);return 0 ; }
@@ -2015,6 +2028,7 @@ static inline int remap_pud_range(struct mm_struct *mm, p4d_t *p4d, {pud_t *pud;unsigned long next; +int err; pfn -= addr >>PAGE_SHIFT ; pud =pud_alloc (mm, p4d, addr);
@@ -2022,9 +2036,10 @@ static inline int remap_pud_range(struct mm_struct *mm, p4d_t *p4d,return -ENOMEM ;do { next =pud_addr_end (addr, end); -if (remap_pmd_range (mm, pud, addr, next, - pfn + (addr >>PAGE_SHIFT ), prot)) -return -ENOMEM ; + err =remap_pmd_range (mm, pud, addr, next, + pfn + (addr >>PAGE_SHIFT ), prot); +if (err) +return err; }while (pud++, addr = next, addr != end);return 0 ; }
@@ -2035,6 +2050,7 @@ static inline int remap_p4d_range(struct mm_struct *mm, pgd_t *pgd, {p4d_t *p4d;unsigned long next; +int err; pfn -= addr >>PAGE_SHIFT ; p4d =p4d_alloc (mm, pgd, addr);
@@ -2042,9 +2058,10 @@ static inline int remap_p4d_range(struct mm_struct *mm, pgd_t *pgd,return -ENOMEM ;do { next =p4d_addr_end (addr, end); -if (remap_pud_range (mm, p4d, addr, next, - pfn + (addr >>PAGE_SHIFT ), prot)) -return -ENOMEM ; + err =remap_pud_range (mm, p4d, addr, next, + pfn + (addr >>PAGE_SHIFT ), prot); +if (err) +return err; }while (p4d++, addr = next, addr != end);return 0 ; }
mm/mprotect.c +49 -0
61 lines not on this page · raw message
Signature · 1 line
2.14.3
[MODERATED] Re: [PATCH v5 5/8] L1TFv4 0
In reply to a message not in the archiveQuoted 15 lines · speck for Andi Kleen
From: Andi Kleen <[email protected]>
Subject: x86, l1tf: Add sysfs reporting for l1tfL1TF core kernel workarounds are cheap and normally always enabled, However we still want to report in sysfs if the system is vulnerable or mitigated. Add the necessary checks.
- We use the same checks as Meltdown to determine if the system is vulnerable. This excludes some Atom CPUs which don't have this problem.
- We check for the (very unlikely) memory > MAX_PA/2 case
- We check for 32bit non PAE and warnNote this patch will likely conflict with some other workaround patches floating around, but should be straight forward to fix.
...
arch/x86/kernel/cpu/bugs.c ssize_t cpu_show_spec_store_bypass(struct device *dev, struct device_attribute * · line 714
Quoted 13 lines
arch/x86/kernel/cpu/bugs.c +5
-0@@ -707,4 +707,15 @@ ssize_t cpu_show_spec_store_bypass(struct device *dev, struct device_attribute * {return cpu_show_common (dev, attr, buf,X86_BUG_SPEC_STORE_BYPASS ); } + +ssize_t cpu_show_l1tf (struct device *dev,struct device_attribute *attr,char *buf) +{ +if (!boot_cpu_has_bug (X86_BUG_L1TF )) +return sprintf (buf,"Not affected\n" );
In your other patches you should test the bug flag X86_BUG_L1TF like here, not with boot_cpu_has().
It works now but we might enforce it someday.
arch/x86/kernel/cpu/bugs.c ssize_t cpu_show_spec_store_bypass(struct device *dev, struct device_attribute * · line 720
Quoted 6 lines
arch/x86/kernel/cpu/bugs.c +6
-0+ +if (boot_cpu_has (X86_FEATURE_L1TF_WA )) +return sprintf (buf,"Mitigated\n" ); + +return sprintf (buf,"Vulnerable\n" ); +}
Make that cpu_show_l1tf() call cpu_show_common() like the others do.
Signature · 5 lines
Regards/Gruss,
Boris.
SUSE Linux GmbH, GF: Felix Imendörffer, Jane Smithard, Graham Norton, HRB 21284 (AG Nürnberg)
-- Dave Hansen
maintainer[MODERATED] Re: [PATCH v5 8/8] L1TFv4 1
In reply to a message not in the archive...
Quoted 13 lines
(quoted diff) +13
-0+bool pfn_modify_allowed(unsigned long pfn, pgprot_t prot) +{ + if (!boot_cpu_has(X86_BUG_L1TF)) + return true; + if (!__pte_needs_invert(pgprot_val(prot))) + return true; + /* If it's real memory always allow */ + if (pfn_valid(pfn)) + return true; + if (pfn > l1tf_pfn_limit() && !capable(CAP_SYS_ADMIN)) + return false; + return true; +}
...
Review
Quoted 12 lines
(quoted diff) +12
-0+ /* + * Do PROT_NONE PFN permission checks here when we can still + * bail out without undoing a lot of state. This is a rather + * uncommon case, so doesn't need to be very optimized. + */ + if (arch_has_pfn_modify_check() && + (vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) && + (newflags & (VM_READ|VM_WRITE|VM_EXEC)) == 0) { + error = prot_none_walk(vma, start, end, newflags); + if (error) + return error; + }
Is it worth moving the boot_cpu_has(X86_BUG_L1TF) check into arch_has_pfn_modify_check()?
As it stands now, we will always unconditionally do the prot_none_walk() for vulnerable VMAs, but we don't even have to do *that* if we are not vulnerable.
It probably doesn't matter as you mentioned because doing an mprotect(PROT_NONE) on a VM_PFNMAP|VM_MIXEDMAP VMA is likely to be rare, but we might as well avoid the walk if we can.
Dave Hansen
maintainer[MODERATED] Re: [PATCH v5 1/8] L1TFv4 6
In reply to a message not in the archiveQuoted 14 lines · speck for Andi Kleen
From: Andi Kleen <[email protected]>
Subject: x86, l1tf: Increase 32bit PAE__PHYSICAL_PAGE_MASKOn 32bit PAE the max PTE mask is currently set to 44 bit because that is the limit imposed by 32bit unsigned long PFNs in the VMs.
The L1TF
PROT_NONEprotection code uses the PTE masks to determine what bits to invert to make sure the higher bits are set for unmapped entries to prevent L1TF speculation attacks against EPT inside guests.But our inverted mask has to match the host, and the host is likely 64bit and may use more than 43 bits of memory. We want to set all possible bits to be safe here.
Quoted 8 lines
So increase the mask on 32bit PAE to 52 to match 64bit.
The real limit is still 44 bits.
All Linux PTEs are created from unsigned long PFNs, so cannot be higher than 44 bits on a 32bit kernel. So these extra PFN bits should be never set. The only users of this macro are using it to look at PTEs, so it's safe.
I'm scratching my head a bit at this description. I've written another summary so I could understand it a bit better. Could you check if my understanding is correct. Feel free to steal any of this for your description or toss it.
BTW, this reminds me: Let's say we trust guest kernels. Do we need KVM code to _prevent_ guests running in 32-bit non-PAE mode? Wouldn't any 32-bit non-PAE guest effectively have the ability to read the bottom ~4GB of host memory?
If we don't trust guest kernels, then what is the point of this patch? :)
Problem:
This patch is intended to protect against a 32-bit unprivileged guest application using PROT_NONE on normal guest memory to attack host memory.
Background:
32-bit 'unsigned long' PFNs can only point to 44 bits of memory (32+PAGE_SHIFT). We enforce this via __PHYSICAL_PAGE_MASK, but unfortunately our L1TF workaround bits are also limited by __PHYSICAL_PAGE_MASK as well.
Example:
Imagine a 32-bit PAE PTE pointing to memory at guest physical address 1GB:
0x0000000040000067
Then the attacker calls mprotect(PROT_NONE). We invert the PTE's physical address bits (and add _PAGE_PROT_NONE), but only those bits set in __PHYSICAL_PAGE_MASK. We get:
0x00000fffbffff100
Which is an address just below 16TB. This might allow an attacker to read *host* memory at that address.
The fix:
Increase __PHYSICAL_PAGE_MASK to allow us to set higher address bits in the inverted PTEs.... and then all the other description you had.
[MODERATED] Re: [PATCH v5 1/8] L1TFv4 6
Quoted 4 lines · speck for Dave Hansen
BTW, this reminds me: Let's say we trust guest kernels. Do we need KVM code to _prevent_ guests running in 32-bit non-PAE mode? Wouldn't any 32-bit non-PAE guest effectively have the ability to read the bottom ~4GB of host memory?
The native and hypervisor fixes are very different.
Native systems use this PTE trick to keep their own userspace out of the rest of the system.
Hypervisors, one way or another, need to ensure that there is nothing interesting a guest (malicious or otherwise) can snoop on. The operating mode doesn't really make any difference from an attack surface perspective. The base case for mitigation is to disable HT and issue an L1D flush on every return to guest path, but there is a lot of work going in to trying to find lower overhead alternatives.
For the avoidance of any doubt, VMs can read their VMCS, EPT tables, IO/MSR bitmaps, etc, and memory mapped by an L1TF-vulnerable PTE in the EPT tables (which had really better only be PFN 0). So long as the kernel is suitably protecting itself from its userspace with the PTE trick, the only interesting information it can get at (we think!) is its layout in host address space.
~Andrew
[MODERATED] Re: [PATCH v5 4/8] L1TFv4 8
In reply to a message not in the archiveQuoted 22 lines · speck for Andi Kleen
From: Andi Kleen <[email protected]>
Subject: x86, l1tf: Make sure the first page is always reservedThe L1TF workaround doesn't make any attempt to mitigate speculate accesses to the first physical page for zeroed PTEs. Normally it only contains some data from the early real mode BIOS.
I couldn't convince myself we always reserve the first page in all configurations, so add an extra reservation call to make sure it is really reserved. In most configurations (e.g. with the standard reservations) it's likely a nop.
Signed-off-by Andi Kleen <[email protected]>--- arch/x86/kernel/setup.c | 3 +++ 1 file changed, 3 insertions(+)arch/x86/kernel/setup.c +0
-0
arch/x86/kernel/setup.c void __init setup_arch(char **cmdline_p) · line 828
Quoted 6 lines
arch/x86/kernel/setup.c +3
-0@@ -823,6 +823,9 @@ void __init setup_arch(char **cmdline_p)memblock_reserve (__pa_symbol (_text), (unsigned long )__bss_stop - (unsigned long )_text); +/* Make sure page 0 is always reserved */ +memblock_reserve (0 ,PAGE_SIZE ); +
A comment about *why* page 0 is reserved would be helpful.
Signature · 1 line
Josh
[MODERATED] Re: [PATCH v5 6/8] L1TFv4 4
In reply to a message not in the archiveQuoted 9 lines · speck for Andi Kleen
From: Andi Kleen <[email protected]>
Subject: x86, l1tf: Report if too much memory for L1TF
workaroundIf the system has more than MAX_PA/2 physical memory the invert page workarounds don't protect the system against the L1TF attack anymore, because an inverted physical address will point to valid memory.
Quoted 6 lines
We cannot do much here, after all users want to use the memory, but at least print a warning and report the system as vulnerable in sysfs
Note this is all extremely unlikely to happen on a real machine because they typically have far more
MAX_PAthan DIMM slots
These paragraphs are missing periods
Quoted 6 lines
Some VMs also report fairly small PAs to guest, e.g. only 36bits. In this case the threshold will be lower, but applies only to the maximum guest size.
Since this needs to clear a feature bit that has been forced earlier add a special "unforce" macro that supports this.
I didn't see an unforce macro, was it from an earlier version of the patch?
Quoted 3 lines
Signed-off-by Andi Kleen <[email protected]>squash! x86, l1tf: Report if too much memory for L1TF workaround
git-rebase casualty?
Quoted 14 lines
v2:
Do force clearing insetup_clear_... Rename variables, fix comments, change formatting. Usel1tf_pfn_limit()--- arch/x86/kernel/cpu/cpuid-deps.c | 1 + arch/x86/kernel/setup.c | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-)arch/x86/kernel/cpu/cpuid-deps.c +0
-0
arch/x86/kernel/cpu/cpuid-deps.c void clear_cpu_cap(struct cpuinfo_x86 *c, unsigned int feature) · line 121
Quoted 4 lines
arch/x86/kernel/cpu/cpuid-deps.c +1
-0@@ -118,4 +118,5 @@ void clear_cpu_cap(struct cpuinfo_x86 *c, unsigned int feature)void setup_clear_cpu_cap (unsigned int feature) {do_clear_cpu_cap (NULL , feature); +clear_bit (feature, (unsigned long *)cpu_caps_set);
The caps code is a jungle, so who knows... but isn't this what cpu_caps_cleared is for? Aren't the cpu_caps_set bits supposed to stay set (for some mysterious reason)?
AFAICT, the cpu_caps_cleared bit already gets set in do_clear_cpu_cap() -> clear_feature(). So I don't see that clearing the cpu_caps_set bit actually accomplishes anything on top of that.
Quoted 13 lines
arch/x86/kernel/cpu/cpuid-deps.c +0
-0}arch/x86/kernel/setup.c +3
-1@@ -785,7 +785,26 @@ static void __init trim_low_memory_range(void) {memblock_reserve (0 ,ALIGN (reserve_low,PAGE_SIZE )); } - + +static __init void check_maxpa_memory (void ) +{
arch/x86/kernel/setup.c static void __init trim_low_memory_range(void) · line 800
Quoted 10 lines
arch/x86/kernel/setup.c +10
-0+u64 half_pa; + +if (!boot_cpu_has (X86_BUG_L1TF )) +return ; + + half_pa = (u64 )l1tf_pfn_limit () <<PAGE_SHIFT ; + +/* +* This is extremely unlikely to happen because almost all systems have far +* more MAX_PA/2 than DIMM slots.
s/DIMM slots/actual RAM/?
Also is MAX_PA a macro or something else? I don't see it in the code anywhere but it's mentioned in several places in these patches (including the printk below).
Quoted 11 lines
arch/x86/kernel/setup.c +7
-0+*/ +if (e820__mapped_any (half_pa,ULLONG_MAX - half_pa,E820_TYPE_RAM )) { +pr_warn ("System has more than MAX_PA/2 memory. Disabled L1TF workaround\n" ); +setup_clear_cpu_cap (X86_FEATURE_L1TF_WA ); + } +} +/* * Dump out kernel offset information on panic. */
arch/x86/kernel/setup.c void __init setup_arch(char **cmdline_p) · line 1045
Quoted 5 lines
arch/x86/kernel/setup.c +2
-0@@ -1022,6 +1041,8 @@ void __init setup_arch(char **cmdline_p)insert_resource (&iomem_resource, &data_resource);insert_resource (&iomem_resource, &bss_resource); +check_maxpa_memory (); +
The function should probably have "l1tf" in its name to make its purpose more clear without having to read it.
Quoted 5 lines
arch/x86/kernel/setup.c +0
-0e820_add_kernel_range ();trim_bios_range ();#ifdef CONFIG_X86_32 Signature · 1 line
2.14.3
Signature · 1 line
Josh
Michal Hocko
reviewer[MODERATED] Re: [PATCH v5 1/8] L1TFv4 6
In reply to Dave HansenQuoted 60 lines · speck for Dave Hansen
Quoted 14 lines · speck for Andi Kleen
From: Andi Kleen <[email protected]>
Subject: x86, l1tf: Increase 32bit PAE__PHYSICAL_PAGE_MASKOn 32bit PAE the max PTE mask is currently set to 44 bit because that is the limit imposed by 32bit unsigned long PFNs in the VMs.
The L1TF
PROT_NONEprotection code uses the PTE masks to determine what bits to invert to make sure the higher bits are set for unmapped entries to prevent L1TF speculation attacks against EPT inside guests.But our inverted mask has to match the host, and the host is likely 64bit and may use more than 43 bits of memory. We want to set all possible bits to be safe here.
Quoted 8 lines
So increase the mask on 32bit PAE to 52 to match 64bit.
The real limit is still 44 bits.
All Linux PTEs are created from unsigned long PFNs, so cannot be higher than 44 bits on a 32bit kernel. So these extra PFN bits should be never set. The only users of this macro are using it to look at PTEs, so it's safe.
I'm scratching my head a bit at this description. I've written another summary so I could understand it a bit better. Could you check if my understanding is correct. Feel free to steal any of this for your description or toss it.
BTW, this reminds me: Let's say we trust guest kernels. Do we need KVM code to _prevent_ guests running in 32-bit non-PAE mode? Wouldn't any 32-bit non-PAE guest effectively have the ability to read the bottom ~4GB of host memory?
If we don't trust guest kernels, then what is the point of this patch? :)
Problem:
This patch is intended to protect against a 32-bit unprivileged guest application using
PROT_NONEon normal guest memory to attack host memory.Background:
32-bit 'unsigned long' PFNs can only point to 44 bits of memory (32+PAGE_SHIFT). We enforce this via
__PHYSICAL_PAGE_MASK, but unfortunately our L1TF workaround bits are also limited by__PHYSICAL_PAGE_MASKas well.Example:
Imagine a 32-bit PAE PTE pointing to memory at guest physical address 1GB:
0x0000000040000067Then the attacker calls
mprotect(PROT_NONE). We invert the PTE's physical address bits (and add_PAGE_PROT_NONE), but only those bits set in__PHYSICAL_PAGE_MASK. We get:0x00000fffbffff100
Quoted 7 lines
Which is an address just below 16TB. This might allow an attacker to read *host* memory at that address.
The fix:
Increase
__PHYSICAL_PAGE_MASKto allow us to set higher address bits in the inverted PTEs.... and then all the other description you had.
This way much easier to grasp than the original changelog. I vote for including it.
Thanks Dave
Signature · 2 lines
Michal Hocko SUSE Labs
[MODERATED] Re: [PATCH v5 6/8] L1TFv4 4
In reply to Josh PoimboeufQuoted 4 lines · speck for Josh Poimboeuf
Quoted 4 lines
index
2c0bd38a44ab..ffb178fe6356100644arch/x86/kernel/cpu/cpuid-deps.c +0
-0@@ -118,4 +118,5 @@ void clear_cpu_cap(struct cpuinfo_x86 *c, unsigned int feature)
Quoted 8 lines
arch/x86/kernel/cpu/cpuid-deps.c void clear_cpu_cap(struct cpuinfo_x86 *c, unsigned int feature) · line 121
Quoted 4 lines
arch/x86/kernel/cpu/cpuid-deps.c +1
-0void setup_clear_cpu_cap (unsigned int feature) {do_clear_cpu_cap (NULL , feature); +clear_bit (feature, (unsigned long *)cpu_caps_set);The caps code is a jungle, so who knows... but isn't this what
cpu_caps_clearedis for? Aren't thecpu_caps_setbits supposed to stay set (for some mysterious reason)?
Set beats cleared without this change. I tested it.
Quoted 4 lines
AFAICT, the
cpu_caps_clearedbit already gets set indo_clear_cpu_cap()->clear_feature(). So I don't see that clearing thecpu_caps_setbit actually accomplishes anything on top of that.
The patch is needed.
Quoted 18 lines
Quoted 18 lines
arch/x86/kernel/cpu/cpuid-deps.c +0
-0}arch/x86/kernel/setup.c +7
-1@@ -785,7 +785,26 @@ static void __init trim_low_memory_range(void) {memblock_reserve (0 ,ALIGN (reserve_low,PAGE_SIZE )); } - + +static __init void check_maxpa_memory (void ) +{ +u64 half_pa; + +if (!boot_cpu_has (X86_BUG_L1TF )) +return ;
Quoted 8 lines
arch/x86/kernel/setup.c static void __init trim_low_memory_range(void) · line 800
Quoted 6 lines
arch/x86/kernel/setup.c +6
-0+ + half_pa = (u64 )l1tf_pfn_limit () <<PAGE_SHIFT ; + +/* +* This is extremely unlikely to happen because almost all systems have far +* more MAX_PA/2 than DIMM slots. s/DIMM slots/actual RAM/?
It's the same in this case.
Quoted 2 lines
Also is
MAX_PAa macro or something else? I don't see it in the code
It's a logical construct but doesn't correspond to a macro in the code.
-Andi
[MODERATED] Re: [PATCH v5 1/8] L1TFv4 6
In reply to Dave HansenQuoted 4 lines
BTW, this reminds me: Let's say we trust guest kernels. Do we need KVM code to _prevent_ guests running in 32-bit non-PAE mode? Wouldn't any 32-bit non-PAE guest effectively have the ability to read the bottom ~4GB of host memory?
If you really trust the guest kernel you need to also trust it to not use PAE.
If the host has full flush mitigations there is no need to trust the host kernel, and it can even use PAE.
Quoted 2 lines
If we don't trust guest kernels, then what is the point of this patch? :)
See below.
Quoted 5 lines
Problem:
This patch is intended to protect against a 32-bit unprivileged guest application using
PROT_NONEon normal guest memory to attack host memory.
No, it does not protect host memory, it protects memory inside the guest.
This is explained in detail in the earlier patch:
Quoted 1 line
Quoted 1 line
Quoted 1 line
Q: Why does the guest need to be protected when the HyperVisor already has L1TF mitigations?
A: Here's an example:
You have physical pages 1 2. They get mapped into a guest as GPA 1 -> PA 2
GPA 2 -> PA 1
through EPT.
The L1TF speculation ignores the EPT remapping.
Now the guest kernel maps GPA 1 to process A and GPA 2 to process B, and they belong to different users and should be isolated.
A sets the GPA 1 PA 2 PTE to PROT_NONE to bypass the EPT remapping and gets read access to the underlying physical page. Which in this case points to PA 2, so it can read process B's data, if it happened to be in L1.
So we broke isolation inside the guest.
There's nothing the hypervisor can do about this. This mitigation has to be done in the guest.
<<<
Quoted 12 lines
Background:
32-bit 'unsigned long' PFNs can only point to 44 bits of memory (32+PAGE_SHIFT). We enforce this via
__PHYSICAL_PAGE_MASK, but unfortunately our L1TF workaround bits are also limited by__PHYSICAL_PAGE_MASKas well.Example:
Imagine a 32-bit PAE PTE pointing to memory at guest physical address 1GB:
Quoted 7 lines
0x0000000040000067Then the attacker calls
mprotect(PROT_NONE). We invert the PTE's physical address bits (and add_PAGE_PROT_NONE), but only those bits set in__PHYSICAL_PAGE_MASK. We get:0x00000fffbffff100
These three paragraphs are correct and I can add them.
-Andi
[MODERATED] Re: [PATCH v5 6/8] L1TFv4 4
In reply to Andi KleenQuoted 14 lines · speck for Andi Kleen
Quoted 4 lines · speck for Josh Poimboeuf
Quoted 4 lines
index
2c0bd38a44ab..ffb178fe6356100644arch/x86/kernel/cpu/cpuid-deps.c +0
-0@@ -118,4 +118,5 @@ void clear_cpu_cap(struct cpuinfo_x86 *c, unsigned int feature)Quoted 8 lines
arch/x86/kernel/cpu/cpuid-deps.c void clear_cpu_cap(struct cpuinfo_x86 *c, unsigned int feature) · line 121
Quoted 4 lines
arch/x86/kernel/cpu/cpuid-deps.c +1
-0void setup_clear_cpu_cap (unsigned int feature) {do_clear_cpu_cap (NULL , feature); +clear_bit (feature, (unsigned long *)cpu_caps_set);The caps code is a jungle, so who knows... but isn't this what
cpu_caps_clearedis for? Aren't thecpu_caps_setbits supposed to stay set (for some mysterious reason)?
Quoted 8 lines
Set beats cleared without this change. I tested it.
Quoted 4 lines
AFAICT, the
cpu_caps_clearedbit already gets set indo_clear_cpu_cap()->clear_feature(). So I don't see that clearing thecpu_caps_setbit actually accomplishes anything on top of that.The patch is needed.
You're right, in apply_forced_caps(), the 'set' bits do override the 'cleared' bits.
I think what confused me is that setup_clear_cpu_cap() has 58 other callers, and none of them ever needed the above change, which seems like a red flag.
BTW, X86_FEATURE_L1TF_WA is never actually used anywhere, shouldn't max_swapfile_size() and pfn_modify_allowed() be checking it instead of X86_BUG_L1TF?
Also, X86_FEATURE_L1TF_WA is highly inscrutable, how about we at least get rid of the "WA" acronym: X86_FEATURE_L1TF_FIX?
Quoted 26 lines
Quoted 17 lines
Quoted 17 lines
arch/x86/kernel/cpu/cpuid-deps.c +0
-0}arch/x86/kernel/setup.c +7
-1@@ -785,7 +785,26 @@ static void __init trim_low_memory_range(void) {memblock_reserve (0 ,ALIGN (reserve_low,PAGE_SIZE )); } - + +static __init void check_maxpa_memory (void ) +{ +u64 half_pa; + +if (!boot_cpu_has (X86_BUG_L1TF )) +return ;Quoted 8 lines
arch/x86/kernel/setup.c static void __init trim_low_memory_range(void) · line 800
Quoted 6 lines
arch/x86/kernel/setup.c +6
-0+ + half_pa = (u64 )l1tf_pfn_limit () <<PAGE_SHIFT ; + +/* +* This is extremely unlikely to happen because almost all systems have far +* more MAX_PA/2 than DIMM slots. s/DIMM slots/actual RAM/?
Quoted 1 line
It's the same in this case.
My computer only has four DIMM slots, how many billions does yours have?
Quoted 3 lines
Quoted 1 line
Also is
MAX_PAa macro or something else? I don't see it in the codeIt's a logical construct but doesn't correspond to a macro in the code.
It's a CONFUSING_CONSTRUCT.
Signature · 1 line
Josh
[MODERATED] Re: [PATCH v5 6/8] L1TFv4 4
Quoted 1 line
BTW,
X86_FEATURE_L1TF_WAis never actually used anywhere, shouldn't
It is used by the sysfs code. This is only used for reporting.
Quoted 2 lines
max_swapfile_size()andpfn_modify_allowed()be checking it instead ofX86_BUG_L1TF?
No, we should try our best to protect even in this case.
-Andi
Dave Hansen
maintainer[MODERATED] Re: [PATCH v5 1/8] L1TFv4 6
In reply to Andi KleenQuoted 4 lines · speck for Andi Kleen
A sets the GPA 1 PA 2 PTE to
PROT_NONEto bypass the EPT remapping and gets read access to the underlying physical page. Which in this case points to PA 2, so it can read process B's data, if it happened to be in L1.
So this is entirely about a 32-bit PAE guest being able to provide guest-process->guest-process isolation?
This is the part I don't understand. Shouldn't the PROT_NONE mitigations have already been applied to the PTEs? That mitigation should keep them from being exploited to read other guest process's data.
[MODERATED] Re: [PATCH v5 6/8] L1TFv4 4
In reply to Andi KleenQuoted 8 lines · speck for Andi Kleen
Quoted 1 line
BTW,
X86_FEATURE_L1TF_WAis never actually used anywhere, shouldn'tIt is used by the sysfs code. This is only used for reporting.
Quoted 2 lines
max_swapfile_size()andpfn_modify_allowed()be checking it instead ofX86_BUG_L1TF?No, we should try our best to protect even in this case.
In that case the "Disabled L1TF workaround" printk and the L1TF_WA feature are misleading. Even with X86_FEATURE_L1TF_WA cleared, the workaround is still in place, it just might not be 100% effective.
Signature · 1 line
Josh
[MODERATED] Re: [PATCH v5 6/8] L1TFv4 4
Quoted 3 lines
In that case the "Disabled L1TF workaround" printk and the
L1TF_WAfeature are misleading. Even withX86_FEATURE_L1TF_WAcleared, the workaround is still in place, it just might not be 100% effective.
I changed the wording of the message to "L1TF workaround not effective."
-Andi