From: Russell Currey <hidden> Date: 2019-12-24 06:07:17
v5 cover letter: https://lore.kernel.org/kernel-hardening/20191030073111.140493-1-ruscur@russell.cc/
v4 cover letter: https://lists.ozlabs.org/pipermail/linuxppc-dev/2019-October/198268.html
v3 cover letter: https://lists.ozlabs.org/pipermail/linuxppc-dev/2019-October/198023.html
Changes since v5:
[1/5]: Addressed review comments from Christophe Leroy (thanks!)
[2/5]: Use patch_instruction() instead of memcpy() thanks to mpe
Thanks for the feedback, hopefully this is the final iteration. I have a patch
to remove the STRICT_KERNEL_RWX incompatibility with RELOCATABLE for book3s64
coming soon, so with that we should have a great basis for powerpc RWX going
forward.
Russell Currey (5):
powerpc/mm: Implement set_memory() routines
powerpc/kprobes: Mark newly allocated probes as RO
powerpc/mm/ptdump: debugfs handler for W+X checks at runtime
powerpc: Set ARCH_HAS_STRICT_MODULE_RWX
powerpc/configs: Enable STRICT_MODULE_RWX in skiroot_defconfig
arch/powerpc/Kconfig | 2 +
arch/powerpc/Kconfig.debug | 6 +-
arch/powerpc/configs/skiroot_defconfig | 1 +
arch/powerpc/include/asm/set_memory.h | 32 ++++++++++
arch/powerpc/kernel/kprobes.c | 6 +-
arch/powerpc/mm/Makefile | 1 +
arch/powerpc/mm/pageattr.c | 83 ++++++++++++++++++++++++++
arch/powerpc/mm/ptdump/ptdump.c | 21 ++++++-
8 files changed, 147 insertions(+), 5 deletions(-)
create mode 100644 arch/powerpc/include/asm/set_memory.h
create mode 100644 arch/powerpc/mm/pageattr.c
--
2.24.1
From: Russell Currey <hidden> Date: 2019-12-24 05:58:04
With CONFIG_STRICT_KERNEL_RWX=y and CONFIG_KPROBES=y, there will be one
W+X page at boot by default. This can be tested with
CONFIG_PPC_PTDUMP=y and CONFIG_PPC_DEBUG_WX=y set, and checking the
kernel log during boot.
powerpc doesn't implement its own alloc() for kprobes like other
architectures do, but we couldn't immediately mark RO anyway since we do
a memcpy to the page we allocate later. After that, nothing should be
allowed to modify the page, and write permissions are removed well
before the kprobe is armed.
The memcpy() would fail if >1 probes were allocated, so use
patch_instruction() instead which is safe for RO.
Reviewed-by: Daniel Axtens <redacted>
Signed-off-by: Russell Currey <redacted>
---
arch/powerpc/kernel/kprobes.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
From: Russell Currey <hidden> Date: 2019-12-24 05:59:46
The set_memory_{ro/rw/nx/x}() functions are required for STRICT_MODULE_RWX,
and are generally useful primitives to have. This implementation is
designed to be completely generic across powerpc's many MMUs.
It's possible that this could be optimised to be faster for specific
MMUs, but the focus is on having a generic and safe implementation for
now.
This implementation does not handle cases where the caller is attempting
to change the mapping of the page it is executing from, or if another
CPU is concurrently using the page being altered. These cases likely
shouldn't happen, but a more complex implementation with MMU-specific code
could safely handle them, so that is left as a TODO for now.
Signed-off-by: Russell Currey <redacted>
---
arch/powerpc/Kconfig | 1 +
arch/powerpc/include/asm/set_memory.h | 32 +++++++++++
arch/powerpc/mm/Makefile | 1 +
arch/powerpc/mm/pageattr.c | 83 +++++++++++++++++++++++++++
4 files changed, 117 insertions(+)
create mode 100644 arch/powerpc/include/asm/set_memory.h
create mode 100644 arch/powerpc/mm/pageattr.c
@@ -0,0 +1,83 @@+// SPDX-License-Identifier: GPL-2.0++/*+*MMU-genericset_memoryimplementationforpowerpc+*+*Copyright2019,IBMCorporation.+*/++#include<linux/mm.h>+#include<linux/set_memory.h>++#include<asm/mmu.h>+#include<asm/page.h>+#include<asm/pgtable.h>+++/*+*Updatestheattributesofapageinthreesteps:+*+*1.invalidatethepagetableentry+*2.flushtheTLB+*3.installthenewentrywiththeupdatedattributes+*+*Thisisunsafeifthecallerisattemptingtochangethemappingofthe+*pageitisexecutingfrom,orifanotherCPUisconcurrentlyusingthe+*pagebeingaltered.+*+*TODOmaketheimplementationresistanttothis.+*/+staticint__change_page_attr(pte_t*ptep,unsignedlongaddr,void*data)+{+intaction=*((int*)data);+pte_tpte_val;++// invalidate the PTE so it's safe to modify+pte_val=ptep_get_and_clear(&init_mm,addr,ptep);+flush_tlb_kernel_range(addr,addr+PAGE_SIZE);++// modify the PTE bits as desired, then apply+switch(action){+caseSET_MEMORY_RO:+pte_val=pte_wrprotect(pte_val);+break;+caseSET_MEMORY_RW:+pte_val=pte_mkwrite(pte_val);+break;+caseSET_MEMORY_NX:+pte_val=pte_exprotect(pte_val);+break;+caseSET_MEMORY_X:+pte_val=pte_mkexec(pte_val);+break;+default:+WARN_ON(true);+return-EINVAL;+}++set_pte_at(&init_mm,addr,ptep,pte_val);++return0;+}++staticintchange_page_attr(pte_t*ptep,unsignedlongaddr,void*data)+{+intret;++spin_lock(&init_mm.page_table_lock);+ret=__change_page_attr(ptep,addr,data);+spin_unlock(&init_mm.page_table_lock);++returnret;+}++intchange_memory_attr(unsignedlongaddr,intnumpages,intaction)+{+unsignedlongstart=ALIGN_DOWN(addr,PAGE_SIZE);+unsignedlongsize=numpages*PAGE_SIZE;++if(!numpages)+return0;++returnapply_to_page_range(&init_mm,start,size,change_page_attr,&action);+}
From: Russell Currey <hidden> Date: 2019-12-24 06:01:43
Very rudimentary, just
echo 1 > [debugfs]/check_wx_pages
and check the kernel log. Useful for testing strict module RWX.
Updated the Kconfig entry to reflect this.
Also fixed a typo.
Signed-off-by: Russell Currey <redacted>
---
arch/powerpc/Kconfig.debug | 6 ++++--
arch/powerpc/mm/ptdump/ptdump.c | 21 ++++++++++++++++++++-
2 files changed, 24 insertions(+), 3 deletions(-)
@@ -370,7 +370,7 @@ config PPC_PTDUMPIfyouareunsure,sayN.configPPC_DEBUG_WX-bool"Warn on W+X mappings at boot"+bool"Warn on W+X mappings at boot & enable manual checks at runtime"depends onPPC_PTDUMPhelpGenerateawarningifanyW+Xmappingsarefoundatboot.
From: Russell Currey <hidden> Date: 2019-12-24 06:03:30
To enable strict module RWX on powerpc, set:
CONFIG_STRICT_MODULE_RWX=y
You should also have CONFIG_STRICT_KERNEL_RWX=y set to have any real
security benefit.
ARCH_HAS_STRICT_MODULE_RWX is set to require ARCH_HAS_STRICT_KERNEL_RWX.
This is due to a quirk in arch/Kconfig and arch/powerpc/Kconfig that
makes STRICT_MODULE_RWX *on by default* in configurations where
STRICT_KERNEL_RWX is *unavailable*.
Since this doesn't make much sense, and module RWX without kernel RWX
doesn't make much sense, having the same dependencies as kernel RWX
works around this problem.
Signed-off-by: Russell Currey <redacted>
---
arch/powerpc/Kconfig | 1 +
1 file changed, 1 insertion(+)
From: Russell Currey <hidden> Date: 2019-12-24 06:05:14
skiroot_defconfig is the only powerpc defconfig with STRICT_KERNEL_RWX
enabled, and if you want memory protection for kernel text you'd want it
for modules too, so enable STRICT_MODULE_RWX there.
Acked-by: Joel Stanley <redacted>
Signed-off-by: Russell Currey <redacted>
---
arch/powerpc/configs/skiroot_defconfig | 1 +
1 file changed, 1 insertion(+)
@@ -370,7 +370,7 @@ config PPC_PTDUMPIfyouareunsure,sayN.configPPC_DEBUG_WX-bool"Warn on W+X mappings at boot"+bool"Warn on W+X mappings at boot & enable manual checks at runtime"depends onPPC_PTDUMPhelpGenerateawarningifanyW+Xmappingsarefoundatboot.
@@ -370,7 +370,7 @@ config PPC_PTDUMPIfyouareunsure,sayN.configPPC_DEBUG_WX-bool"Warn on W+X mappings at boot"+bool"Warn on W+X mappings at boot & enable manual checks at runtime"depends onPPC_PTDUMPhelpGenerateawarningifanyW+Xmappingsarefoundatboot.
The set_memory_{ro/rw/nx/x}() functions are required for STRICT_MODULE_RWX,
and are generally useful primitives to have. This implementation is
designed to be completely generic across powerpc's many MMUs.
It's possible that this could be optimised to be faster for specific
MMUs, but the focus is on having a generic and safe implementation for
now.
This implementation does not handle cases where the caller is attempting
to change the mapping of the page it is executing from, or if another
CPU is concurrently using the page being altered. These cases likely
shouldn't happen, but a more complex implementation with MMU-specific code
could safely handle them, so that is left as a TODO for now.
Signed-off-by: Russell Currey <redacted>
---
arch/powerpc/Kconfig | 1 +
arch/powerpc/include/asm/set_memory.h | 32 +++++++++++
arch/powerpc/mm/Makefile | 1 +
arch/powerpc/mm/pageattr.c | 83 +++++++++++++++++++++++++++
4 files changed, 117 insertions(+)
create mode 100644 arch/powerpc/include/asm/set_memory.h
create mode 100644 arch/powerpc/mm/pageattr.c
CONFIG_ARCH_HAS_SET_MEMORY is set inconditionnally, I think you should
add pageattr.o to obj-y instead. CONFIG_ARCH_HAS_XXX are almost never
used in Makefiles
Don't use pointers for so simple things, pointers forces the compiler to
setup a stack frame and save the data into stack. Instead do:
int action = (int)data;
+ pte_t pte_val;
+
+ // invalidate the PTE so it's safe to modify
+ pte_val = ptep_get_and_clear(&init_mm, addr, ptep);
+ flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
Why flush a range for a single page ? On most targets this will do a
tlbia which is heavy, while a tlbie would suffice.
I think flush_tlb_kernel_range() should be replaced by something
flushing only a single page.
+
+ // modify the PTE bits as desired, then apply
+ switch (action) {
+ case SET_MEMORY_RO:
+ pte_val = pte_wrprotect(pte_val);
+ break;
+ case SET_MEMORY_RW:
+ pte_val = pte_mkwrite(pte_val);
+ break;
+ case SET_MEMORY_NX:
+ pte_val = pte_exprotect(pte_val);
+ break;
+ case SET_MEMORY_X:
+ pte_val = pte_mkexec(pte_val);
+ break;
+ default:
+ WARN_ON(true);
+ return -EINVAL;
Is it worth checking that the action is valid for each page ? I think
validity of action should be checked in change_memory_attr(). All other
functions are static so you know they won't be called from outside.
Once done, you can squash __change_page_attr() into change_page_attr(),
remove the ret var and return 0 all the time.
+ }
+
+ set_pte_at(&init_mm, addr, ptep, pte_val);
+
+ return 0;
+}
+
+static int change_page_attr(pte_t *ptep, unsigned long addr, void *data)
+{
+ int ret;
+
+ spin_lock(&init_mm.page_table_lock);
+ ret = __change_page_attr(ptep, addr, data);
+ spin_unlock(&init_mm.page_table_lock);
+
+ return ret;
+}
+
+int change_memory_attr(unsigned long addr, int numpages, int action)
+{
+ unsigned long start = ALIGN_DOWN(addr, PAGE_SIZE);
+ unsigned long size = numpages * PAGE_SIZE;
+
+ if (!numpages)
+ return 0;
+
+ return apply_to_page_range(&init_mm, start, size, change_page_attr, &action);
Use (void*)action instead of &action (see upper comment)
With CONFIG_STRICT_KERNEL_RWX=y and CONFIG_KPROBES=y, there will be one
W+X page at boot by default. This can be tested with
CONFIG_PPC_PTDUMP=y and CONFIG_PPC_DEBUG_WX=y set, and checking the
kernel log during boot.
powerpc doesn't implement its own alloc() for kprobes like other
architectures do, but we couldn't immediately mark RO anyway since we do
a memcpy to the page we allocate later. After that, nothing should be
allowed to modify the page, and write permissions are removed well
before the kprobe is armed.
The memcpy() would fail if >1 probes were allocated, so use
patch_instruction() instead which is safe for RO.
Reviewed-by: Daniel Axtens <redacted>
Signed-off-by: Russell Currey <redacted>
---
arch/powerpc/kernel/kprobes.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
I don't really understand, why do you need to set this ro ? Or why do
you need to change the memcpy() to patch_instruction() if the area is
not already ro ?
If I understand correctly, p->ainsn.insn is within a special executable
page allocated via module_alloc(). Wouldn't it be more correct to modify
kprobe get_insn_slot() logic so that allocated page is ROX instead of RWX ?
The set_memory_{ro/rw/nx/x}() functions are required for STRICT_MODULE_RWX,
and are generally useful primitives to have. This implementation is
designed to be completely generic across powerpc's many MMUs.
It's possible that this could be optimised to be faster for specific
MMUs, but the focus is on having a generic and safe implementation for
now.
This implementation does not handle cases where the caller is attempting
to change the mapping of the page it is executing from, or if another
CPU is concurrently using the page being altered. These cases likely
shouldn't happen, but a more complex implementation with MMU-specific code
could safely handle them, so that is left as a TODO for now.
Signed-off-by: Russell Currey <redacted>
---
arch/powerpc/Kconfig | 1 +
arch/powerpc/include/asm/set_memory.h | 32 +++++++++++
arch/powerpc/mm/Makefile | 1 +
arch/powerpc/mm/pageattr.c | 83 +++++++++++++++++++++++++++
4 files changed, 117 insertions(+)
create mode 100644 arch/powerpc/include/asm/set_memory.h
create mode 100644 arch/powerpc/mm/pageattr.c
+static int __change_page_attr(pte_t *ptep, unsigned long addr, void *data)
+{
+ int action = *((int *)data);
+ pte_t pte_val;
pte_val is really not a good naming, because pte_val() is already a
function which returns the value of a pte_t var.
Here you should name it 'pte' as usual.
Christophe
+
+ // invalidate the PTE so it's safe to modify
+ pte_val = ptep_get_and_clear(&init_mm, addr, ptep);
+ flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
+
+ // modify the PTE bits as desired, then apply
+ switch (action) {
+ case SET_MEMORY_RO:
+ pte_val = pte_wrprotect(pte_val);
+ break;
+ case SET_MEMORY_RW:
+ pte_val = pte_mkwrite(pte_val);
+ break;
+ case SET_MEMORY_NX:
+ pte_val = pte_exprotect(pte_val);
+ break;
+ case SET_MEMORY_X:
+ pte_val = pte_mkexec(pte_val);
+ break;
+ default:
+ WARN_ON(true);
+ return -EINVAL;
+ }
+
+ set_pte_at(&init_mm, addr, ptep, pte_val);
+
+ return 0;
+}
+
From: Russell Currey <hidden> Date: 2020-02-03 00:48:24
On Wed, 2020-01-08 at 13:52 +0100, Christophe Leroy wrote:
Le 24/12/2019 à 06:55, Russell Currey a écrit :
quoted
The set_memory_{ro/rw/nx/x}() functions are required for
STRICT_MODULE_RWX,
and are generally useful primitives to have. This implementation
is
designed to be completely generic across powerpc's many MMUs.
It's possible that this could be optimised to be faster for
specific
MMUs, but the focus is on having a generic and safe implementation
for
now.
This implementation does not handle cases where the caller is
attempting
to change the mapping of the page it is executing from, or if
another
CPU is concurrently using the page being altered. These cases
likely
shouldn't happen, but a more complex implementation with MMU-
specific code
could safely handle them, so that is left as a TODO for now.
Signed-off-by: Russell Currey <redacted>
---
arch/powerpc/Kconfig | 1 +
arch/powerpc/include/asm/set_memory.h | 32 +++++++++++
arch/powerpc/mm/Makefile | 1 +
arch/powerpc/mm/pageattr.c | 83
+++++++++++++++++++++++++++
4 files changed, 117 insertions(+)
create mode 100644 arch/powerpc/include/asm/set_memory.h
create mode 100644 arch/powerpc/mm/pageattr.c
CONFIG_ARCH_HAS_SET_MEMORY is set inconditionnally, I think you
should
add pageattr.o to obj-y instead. CONFIG_ARCH_HAS_XXX are almost
never
used in Makefiles
Fair enough, will keep that in mind
quoted
diff --git a/arch/powerpc/mm/pageattr.c
b/arch/powerpc/mm/pageattr.c
new file mode 100644
index 000000000000..15d5fb04f531
mapping of the
+ * page it is executing from, or if another CPU is concurrently
using the
+ * page being altered.
+ *
+ * TODO make the implementation resistant to this.
+ */
+static int __change_page_attr(pte_t *ptep, unsigned long addr,
void *data)
+{
+ int action = *((int *)data);
Don't use pointers for so simple things, pointers forces the compiler
to
setup a stack frame and save the data into stack. Instead do:
int action = (int)data;
quoted
+ pte_t pte_val;
+
+ // invalidate the PTE so it's safe to modify
+ pte_val = ptep_get_and_clear(&init_mm, addr, ptep);
+ flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
Why flush a range for a single page ? On most targets this will do a
tlbia which is heavy, while a tlbie would suffice.
I think flush_tlb_kernel_range() should be replaced by something
flushing only a single page.
You might be able to help me out here, I wanted to do that but the only
functions I could find that flushed single pages needed a
vm_area_struct, which I can't get.
quoted
+
+ // modify the PTE bits as desired, then apply
+ switch (action) {
+ case SET_MEMORY_RO:
+ pte_val = pte_wrprotect(pte_val);
+ break;
+ case SET_MEMORY_RW:
+ pte_val = pte_mkwrite(pte_val);
+ break;
+ case SET_MEMORY_NX:
+ pte_val = pte_exprotect(pte_val);
+ break;
+ case SET_MEMORY_X:
+ pte_val = pte_mkexec(pte_val);
+ break;
+ default:
+ WARN_ON(true);
+ return -EINVAL;
Is it worth checking that the action is valid for each page ? I
think
validity of action should be checked in change_memory_attr(). All
other
functions are static so you know they won't be called from outside.
Once done, you can squash __change_page_attr() into
change_page_attr(),
remove the ret var and return 0 all the time.
Makes sense to fold things into a single function, but in terms of
performance it shouldn't make a difference, right? I still have to
check the action to determine what to change (unless I replace passing
SET_MEMORY_RO into apply_to_page_range() with a function pointer to
pte_wrprotect() for example).
quoted
+ }
+
+ set_pte_at(&init_mm, addr, ptep, pte_val);
+
+ return 0;
+}
+
+static int change_page_attr(pte_t *ptep, unsigned long addr, void
*data)
+{
+ int ret;
+
+ spin_lock(&init_mm.page_table_lock);
+ ret = __change_page_attr(ptep, addr, data);
+ spin_unlock(&init_mm.page_table_lock);
+
+ return ret;
+}
+
+int change_memory_attr(unsigned long addr, int numpages, int
action)
+{
+ unsigned long start = ALIGN_DOWN(addr, PAGE_SIZE);
+ unsigned long size = numpages * PAGE_SIZE;
+
+ if (!numpages)
+ return 0;
+
+ return apply_to_page_range(&init_mm, start, size,
change_page_attr, &action);
Use (void*)action instead of &action (see upper comment)
To get this to work I had to use (void *)(size_t)action to stop the
compiler from complaining about casting an int to a void*, is there a
better way to go about it? Works fine, just looks gross.
CONFIG_ARCH_HAS_SET_MEMORY is set inconditionnally, I think you
should
add pageattr.o to obj-y instead. CONFIG_ARCH_HAS_XXX are almost
never
used in Makefiles
Fair enough, will keep that in mind
I forgot I commented that. I'll do it in v3.
quoted
quoted
+ pte_t pte_val;
+
+ // invalidate the PTE so it's safe to modify
+ pte_val = ptep_get_and_clear(&init_mm, addr, ptep);
+ flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
Why flush a range for a single page ? On most targets this will do a
tlbia which is heavy, while a tlbie would suffice.
I think flush_tlb_kernel_range() should be replaced by something
flushing only a single page.
You might be able to help me out here, I wanted to do that but the only
functions I could find that flushed single pages needed a
vm_area_struct, which I can't get.
+
+ // modify the PTE bits as desired, then apply
+ switch (action) {
+ case SET_MEMORY_RO:
+ pte_val = pte_wrprotect(pte_val);
+ break;
+ case SET_MEMORY_RW:
+ pte_val = pte_mkwrite(pte_val);
+ break;
+ case SET_MEMORY_NX:
+ pte_val = pte_exprotect(pte_val);
+ break;
+ case SET_MEMORY_X:
+ pte_val = pte_mkexec(pte_val);
+ break;
+ default:
+ WARN_ON(true);
+ return -EINVAL;
Is it worth checking that the action is valid for each page ? I
think
validity of action should be checked in change_memory_attr(). All
other
functions are static so you know they won't be called from outside.
Once done, you can squash __change_page_attr() into
change_page_attr(),
remove the ret var and return 0 all the time.
Makes sense to fold things into a single function, but in terms of
performance it shouldn't make a difference, right? I still have to
check the action to determine what to change (unless I replace passing
SET_MEMORY_RO into apply_to_page_range() with a function pointer to
pte_wrprotect() for example).
pte_wrprotect() is a static inline.
quoted
quoted
+ }
+
+ set_pte_at(&init_mm, addr, ptep, pte_val);
+
+ return 0;
+}
+
+static int change_page_attr(pte_t *ptep, unsigned long addr, void
*data)
+{
+ int ret;
+
+ spin_lock(&init_mm.page_table_lock);
+ ret = __change_page_attr(ptep, addr, data);
+ spin_unlock(&init_mm.page_table_lock);
+
+ return ret;
+}
+
+int change_memory_attr(unsigned long addr, int numpages, int
action)
+{
+ unsigned long start = ALIGN_DOWN(addr, PAGE_SIZE);
+ unsigned long size = numpages * PAGE_SIZE;
+
+ if (!numpages)
+ return 0;
+
+ return apply_to_page_range(&init_mm, start, size,
change_page_attr, &action);
Use (void*)action instead of &action (see upper comment)
To get this to work I had to use (void *)(size_t)action to stop the
compiler from complaining about casting an int to a void*, is there a
better way to go about it? Works fine, just looks gross.