diff mbox

[PATCHv2,1/4] arm64: Add CONFIG_DEBUG_SET_MODULE_RONX support

Message ID 1401742658-11841-2-git-send-email-lauraa@codeaurora.org (mailing list archive)
State New, archived
Headers show

Commit Message

Laura Abbott June 2, 2014, 8:57 p.m. UTC
In a similar fashion to other architecture, add the infrastructure
and Kconfig to enable DEBUG_SET_MODULE_RONX support. When
enabled, module ranges will be marked read-only/no-execute as
appropriate.

Signed-off-by: Laura Abbott <lauraa@codeaurora.org>
---
 arch/arm64/Kconfig.debug            |  11 ++++
 arch/arm64/include/asm/cacheflush.h |   4 ++
 arch/arm64/mm/Makefile              |   2 +-
 arch/arm64/mm/pageattr.c            | 121 ++++++++++++++++++++++++++++++++++++
 4 files changed, 137 insertions(+), 1 deletion(-)
 create mode 100644 arch/arm64/mm/pageattr.c

Comments

Will Deacon June 3, 2014, 3:22 p.m. UTC | #1
Hi Laura,

This is looking better, but comments inline.

On Mon, Jun 02, 2014 at 09:57:35PM +0100, Laura Abbott wrote:
> 
> In a similar fashion to other architecture, add the infrastructure
> and Kconfig to enable DEBUG_SET_MODULE_RONX support. When
> enabled, module ranges will be marked read-only/no-execute as
> appropriate.
> 
> Signed-off-by: Laura Abbott <lauraa@codeaurora.org>
> ---
>  arch/arm64/Kconfig.debug            |  11 ++++
>  arch/arm64/include/asm/cacheflush.h |   4 ++
>  arch/arm64/mm/Makefile              |   2 +-
>  arch/arm64/mm/pageattr.c            | 121 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 137 insertions(+), 1 deletion(-)
>  create mode 100644 arch/arm64/mm/pageattr.c

[...]

>  obj-$(CONFIG_HUGETLB_PAGE)	+= hugetlbpage.o
> diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
> new file mode 100644
> index 0000000..d8ab747
> --- /dev/null
> +++ b/arch/arm64/mm/pageattr.c
> @@ -0,0 +1,121 @@
> +/*
> + * Copyright (c) 2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#include <linux/kernel.h>
> +#include <linux/mm.h>
> +#include <linux/sched.h>
> +
> +#include <asm/pgtable.h>
> +#include <asm/tlbflush.h>
> +
> +static pte_t clear_pte_bit(pte_t pte, pgprot_t prot)
> +{
> +	pte_val(pte) &= ~pgprot_val(prot);
> +	return pte;
> +}
> +
> +static pte_t set_pte_bit(pte_t pte, pgprot_t prot)
> +{
> +	pte_val(pte) |= pgprot_val(prot);
> +	return pte;
> +}

We could actually re-use these for building our pte_mk* functions in
pgtable.h. Care to move them there?

> +static int __change_memory(pte_t *ptep, pgtable_t token, unsigned long addr,
> +			pgprot_t prot, bool set)
> +{
> +	pte_t pte;
> +
> +	if (set)
> +		pte = set_pte_bit(*ptep, prot);
> +	else
> +		pte = clear_pte_bit(*ptep, prot);
> +	set_pte(ptep, pte);
> +	return 0;
> +}
> +
> +static int set_page_range(pte_t *ptep, pgtable_t token, unsigned long addr,
> +			void *data)
> +{
> +	pgprot_t prot = (pgprot_t)data;
> +
> +	return __change_memory(ptep, token, addr, prot, true);
> +}
> +
> +static int clear_page_range(pte_t *ptep, pgtable_t token, unsigned long addr,
> +			void *data)
> +{
> +	pgprot_t prot = (pgprot_t)data;
> +
> +	return __change_memory(ptep, token, addr, prot, false);
> +}
> +
> +static int change_memory_common(unsigned long addr, int numpages,
> +				pgprot_t prot, bool set)
> +{
> +	unsigned long start = addr;
> +	unsigned long size = PAGE_SIZE*numpages;
> +	unsigned long end = start + size;
> +	int ret;
> +
> +	if (start < MODULES_VADDR || start >= MODULES_END)
> +		return -EINVAL;
> +
> +	if (end < MODULES_VADDR || end >= MODULES_END)
> +		return -EINVAL;

Can you use is_module_address here, or do you need to change the page
attributes for areas where no modules are currently loaded too?

> +	if (set)
> +		ret = apply_to_page_range(&init_mm, start, size,
> +					set_page_range, (void *)prot);
> +	else
> +		ret = apply_to_page_range(&init_mm, start, size,
> +					clear_page_range, (void *)prot);
> +
> +	flush_tlb_kernel_range(start, end);
> +	isb();
> +	return ret;

We already have an isb in flush_tlb_kernel_range.

> +static int change_memory_set_bit(unsigned long addr, int numpages,
> +					pgprot_t prot)
> +{
> +	return change_memory_common(addr, numpages, prot, true);
> +}
> +
> +static int change_memory_clear_bit(unsigned long addr, int numpages,
> +					pgprot_t prot)
> +{
> +	return change_memory_common(addr, numpages, prot, false);
> +}
> +
> +int set_memory_ro(unsigned long addr, int numpages)
> +{
> +	return change_memory_set_bit(addr, numpages, __pgprot(PTE_RDONLY));
> +}
> +EXPORT_SYMBOL_GPL(set_memory_ro);
> +
> +int set_memory_rw(unsigned long addr, int numpages)
> +{
> +	return change_memory_clear_bit(addr, numpages, __pgprot(PTE_RDONLY));
> +}
> +EXPORT_SYMBOL_GPL(set_memory_rw);

I'm slightly worried about the interaction with this and PTE_WRITE (see
linux-next). If the kernel pages are marked as PTE_DIRTY | PTE_WRITE, then
setting read-only is a weird contradiction. Can you take PTE_WRITE into
account for these two please?

Will
Steve Capper June 3, 2014, 3:31 p.m. UTC | #2
On 3 June 2014 16:22, Will Deacon <will.deacon@arm.com> wrote:

[ ... ]

>
> We already have an isb in flush_tlb_kernel_range.

Hi Will,
The following thread discusses the removal of the isb() from
flush_tlb_kernel_range:
http://lists.infradead.org/pipermail/linux-arm-kernel/2014-May/252829.html

Also, I asked for it to be added to this series in this email thread
but you thought it was benign:
http://lists.infradead.org/pipermail/linux-arm-kernel/2014-May/252869.html
So we could probably do without the isb(.).

Cheers,
--
Steve
Will Deacon June 3, 2014, 3:37 p.m. UTC | #3
On Tue, Jun 03, 2014 at 04:31:57PM +0100, Steve Capper wrote:
> On 3 June 2014 16:22, Will Deacon <will.deacon@arm.com> wrote:
> 
> [ ... ]
> 
> >
> > We already have an isb in flush_tlb_kernel_range.
> 
> Hi Will,
> The following thread discusses the removal of the isb() from
> flush_tlb_kernel_range:
> http://lists.infradead.org/pipermail/linux-arm-kernel/2014-May/252829.html
> 
> Also, I asked for it to be added to this series in this email thread
> but you thought it was benign:
> http://lists.infradead.org/pipermail/linux-arm-kernel/2014-May/252869.html
> So we could probably do without the isb(.).

Bah, I'd completely forgotten all of that! Anyway, I'd say it's up to
the patch removing the existing isb from flush_tlb_kernel_range to fix
the callers, not for other people to anticipate that change.

Even with that change, the isb isn't needed, as mentioned in the second
link above.

Will
Steve Capper June 3, 2014, 4:04 p.m. UTC | #4
On 3 June 2014 16:37, Will Deacon <will.deacon@arm.com> wrote:
> On Tue, Jun 03, 2014 at 04:31:57PM +0100, Steve Capper wrote:
>> On 3 June 2014 16:22, Will Deacon <will.deacon@arm.com> wrote:
>>
>> [ ... ]
>>
>> >
>> > We already have an isb in flush_tlb_kernel_range.
>>
>> Hi Will,
>> The following thread discusses the removal of the isb() from
>> flush_tlb_kernel_range:
>> http://lists.infradead.org/pipermail/linux-arm-kernel/2014-May/252829.html
>>
>> Also, I asked for it to be added to this series in this email thread
>> but you thought it was benign:
>> http://lists.infradead.org/pipermail/linux-arm-kernel/2014-May/252869.html
>> So we could probably do without the isb(.).
>
> Bah, I'd completely forgotten all of that! Anyway, I'd say it's up to
> the patch removing the existing isb from flush_tlb_kernel_range to fix
> the callers, not for other people to anticipate that change.

Agreed.... but Catalin has applied my patch before this version hit the list:
http://lists.infradead.org/pipermail/linux-arm-kernel/2014-May/254828.html

>
> Even with that change, the isb isn't needed, as mentioned in the second
> link above.

Agreed :-).
Laura Abbott June 4, 2014, 12:56 a.m. UTC | #5
On 6/3/2014 8:22 AM, Will Deacon wrote:
> Hi Laura,
> 
> This is looking better, but comments inline.
> 
> On Mon, Jun 02, 2014 at 09:57:35PM +0100, Laura Abbott wrote:
>>
>> In a similar fashion to other architecture, add the infrastructure
>> and Kconfig to enable DEBUG_SET_MODULE_RONX support. When
>> enabled, module ranges will be marked read-only/no-execute as
>> appropriate.
>>
>> Signed-off-by: Laura Abbott <lauraa@codeaurora.org>
>> ---
>>  arch/arm64/Kconfig.debug            |  11 ++++
>>  arch/arm64/include/asm/cacheflush.h |   4 ++
>>  arch/arm64/mm/Makefile              |   2 +-
>>  arch/arm64/mm/pageattr.c            | 121 ++++++++++++++++++++++++++++++++++++
>>  4 files changed, 137 insertions(+), 1 deletion(-)
>>  create mode 100644 arch/arm64/mm/pageattr.c
> 
> [...]
> 
>>  obj-$(CONFIG_HUGETLB_PAGE)	+= hugetlbpage.o
>> diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
>> new file mode 100644
>> index 0000000..d8ab747
>> --- /dev/null
>> +++ b/arch/arm64/mm/pageattr.c
>> @@ -0,0 +1,121 @@
>> +/*
>> + * Copyright (c) 2014, The Linux Foundation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + */
>> +#include <linux/kernel.h>
>> +#include <linux/mm.h>
>> +#include <linux/sched.h>
>> +
>> +#include <asm/pgtable.h>
>> +#include <asm/tlbflush.h>
>> +
>> +static pte_t clear_pte_bit(pte_t pte, pgprot_t prot)
>> +{
>> +	pte_val(pte) &= ~pgprot_val(prot);
>> +	return pte;
>> +}
>> +
>> +static pte_t set_pte_bit(pte_t pte, pgprot_t prot)
>> +{
>> +	pte_val(pte) |= pgprot_val(prot);
>> +	return pte;
>> +}
> 
> We could actually re-use these for building our pte_mk* functions in
> pgtable.h. Care to move them there?
> 

Fine.

>> +static int __change_memory(pte_t *ptep, pgtable_t token, unsigned long addr,
>> +			pgprot_t prot, bool set)
>> +{
>> +	pte_t pte;
>> +
>> +	if (set)
>> +		pte = set_pte_bit(*ptep, prot);
>> +	else
>> +		pte = clear_pte_bit(*ptep, prot);
>> +	set_pte(ptep, pte);
>> +	return 0;
>> +}
>> +
>> +static int set_page_range(pte_t *ptep, pgtable_t token, unsigned long addr,
>> +			void *data)
>> +{
>> +	pgprot_t prot = (pgprot_t)data;
>> +
>> +	return __change_memory(ptep, token, addr, prot, true);
>> +}
>> +
>> +static int clear_page_range(pte_t *ptep, pgtable_t token, unsigned long addr,
>> +			void *data)
>> +{
>> +	pgprot_t prot = (pgprot_t)data;
>> +
>> +	return __change_memory(ptep, token, addr, prot, false);
>> +}
>> +
>> +static int change_memory_common(unsigned long addr, int numpages,
>> +				pgprot_t prot, bool set)
>> +{
>> +	unsigned long start = addr;
>> +	unsigned long size = PAGE_SIZE*numpages;
>> +	unsigned long end = start + size;
>> +	int ret;
>> +
>> +	if (start < MODULES_VADDR || start >= MODULES_END)
>> +		return -EINVAL;
>> +
>> +	if (end < MODULES_VADDR || end >= MODULES_END)
>> +		return -EINVAL;
> 
> Can you use is_module_address here, or do you need to change the page
> attributes for areas where no modules are currently loaded too?
> 

Yes, I think is_module_address should work fine.

>> +	if (set)
>> +		ret = apply_to_page_range(&init_mm, start, size,
>> +					set_page_range, (void *)prot);
>> +	else
>> +		ret = apply_to_page_range(&init_mm, start, size,
>> +					clear_page_range, (void *)prot);
>> +
>> +	flush_tlb_kernel_range(start, end);
>> +	isb();
>> +	return ret;
> 
> We already have an isb in flush_tlb_kernel_range.
> 

Yes, I'll drop the isb here.

>> +static int change_memory_set_bit(unsigned long addr, int numpages,
>> +					pgprot_t prot)
>> +{
>> +	return change_memory_common(addr, numpages, prot, true);
>> +}
>> +
>> +static int change_memory_clear_bit(unsigned long addr, int numpages,
>> +					pgprot_t prot)
>> +{
>> +	return change_memory_common(addr, numpages, prot, false);
>> +}
>> +
>> +int set_memory_ro(unsigned long addr, int numpages)
>> +{
>> +	return change_memory_set_bit(addr, numpages, __pgprot(PTE_RDONLY));
>> +}
>> +EXPORT_SYMBOL_GPL(set_memory_ro);
>> +
>> +int set_memory_rw(unsigned long addr, int numpages)
>> +{
>> +	return change_memory_clear_bit(addr, numpages, __pgprot(PTE_RDONLY));
>> +}
>> +EXPORT_SYMBOL_GPL(set_memory_rw);
> 
> I'm slightly worried about the interaction with this and PTE_WRITE (see
> linux-next). If the kernel pages are marked as PTE_DIRTY | PTE_WRITE, then
> setting read-only is a weird contradiction. Can you take PTE_WRITE into
> account for these two please?
>

It sounds like the solution should be to set/clear PTE_WRITE as appropriate
here, is my understanding correct?

> Will
> 

Thanks,
Laura
Will Deacon June 4, 2014, 6 p.m. UTC | #6
On Wed, Jun 04, 2014 at 01:56:34AM +0100, Laura Abbott wrote:
> On 6/3/2014 8:22 AM, Will Deacon wrote:
> >> +int set_memory_ro(unsigned long addr, int numpages)
> >> +{
> >> +	return change_memory_set_bit(addr, numpages, __pgprot(PTE_RDONLY));
> >> +}
> >> +EXPORT_SYMBOL_GPL(set_memory_ro);
> >> +
> >> +int set_memory_rw(unsigned long addr, int numpages)
> >> +{
> >> +	return change_memory_clear_bit(addr, numpages, __pgprot(PTE_RDONLY));
> >> +}
> >> +EXPORT_SYMBOL_GPL(set_memory_rw);
> > 
> > I'm slightly worried about the interaction with this and PTE_WRITE (see
> > linux-next). If the kernel pages are marked as PTE_DIRTY | PTE_WRITE, then
> > setting read-only is a weird contradiction. Can you take PTE_WRITE into
> > account for these two please?
> >
> 
> It sounds like the solution should be to set/clear PTE_WRITE as appropriate
> here, is my understanding correct?

You got it! You can look at set_pte_at if you're unsure (bearing in mind
that kernel mappings are always dirty).

Will
diff mbox

Patch

diff --git a/arch/arm64/Kconfig.debug b/arch/arm64/Kconfig.debug
index d10ec33..53979ac 100644
--- a/arch/arm64/Kconfig.debug
+++ b/arch/arm64/Kconfig.debug
@@ -37,4 +37,15 @@  config PID_IN_CONTEXTIDR
 	  instructions during context switch. Say Y here only if you are
 	  planning to use hardware trace tools with this kernel.
 
+config DEBUG_SET_MODULE_RONX
+        bool "Set loadable kernel module data as NX and text as RO"
+        depends on MODULES
+        help
+          This option helps catch unintended modifications to loadable
+          kernel module's text and read-only data. It also prevents execution
+          of module data. Such protection may interfere with run-time code
+          patching and dynamic kernel tracing - and they might also protect
+          against certain classes of kernel exploits.
+          If in doubt, say "N".
+
 endmenu
diff --git a/arch/arm64/include/asm/cacheflush.h b/arch/arm64/include/asm/cacheflush.h
index 4c60e64..c12f837 100644
--- a/arch/arm64/include/asm/cacheflush.h
+++ b/arch/arm64/include/asm/cacheflush.h
@@ -157,4 +157,8 @@  static inline void flush_cache_vunmap(unsigned long start, unsigned long end)
 {
 }
 
+int set_memory_ro(unsigned long addr, int numpages);
+int set_memory_rw(unsigned long addr, int numpages);
+int set_memory_x(unsigned long addr, int numpages);
+int set_memory_nx(unsigned long addr, int numpages);
 #endif
diff --git a/arch/arm64/mm/Makefile b/arch/arm64/mm/Makefile
index b51d364..25b1114 100644
--- a/arch/arm64/mm/Makefile
+++ b/arch/arm64/mm/Makefile
@@ -1,5 +1,5 @@ 
 obj-y				:= dma-mapping.o extable.o fault.o init.o \
 				   cache.o copypage.o flush.o \
 				   ioremap.o mmap.o pgd.o mmu.o \
-				   context.o tlb.o proc.o
+				   context.o tlb.o proc.o pageattr.o
 obj-$(CONFIG_HUGETLB_PAGE)	+= hugetlbpage.o
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
new file mode 100644
index 0000000..d8ab747
--- /dev/null
+++ b/arch/arm64/mm/pageattr.c
@@ -0,0 +1,121 @@ 
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+
+#include <asm/pgtable.h>
+#include <asm/tlbflush.h>
+
+static pte_t clear_pte_bit(pte_t pte, pgprot_t prot)
+{
+	pte_val(pte) &= ~pgprot_val(prot);
+	return pte;
+}
+
+static pte_t set_pte_bit(pte_t pte, pgprot_t prot)
+{
+	pte_val(pte) |= pgprot_val(prot);
+	return pte;
+}
+
+static int __change_memory(pte_t *ptep, pgtable_t token, unsigned long addr,
+			pgprot_t prot, bool set)
+{
+	pte_t pte;
+
+	if (set)
+		pte = set_pte_bit(*ptep, prot);
+	else
+		pte = clear_pte_bit(*ptep, prot);
+	set_pte(ptep, pte);
+	return 0;
+}
+
+static int set_page_range(pte_t *ptep, pgtable_t token, unsigned long addr,
+			void *data)
+{
+	pgprot_t prot = (pgprot_t)data;
+
+	return __change_memory(ptep, token, addr, prot, true);
+}
+
+static int clear_page_range(pte_t *ptep, pgtable_t token, unsigned long addr,
+			void *data)
+{
+	pgprot_t prot = (pgprot_t)data;
+
+	return __change_memory(ptep, token, addr, prot, false);
+}
+
+static int change_memory_common(unsigned long addr, int numpages,
+				pgprot_t prot, bool set)
+{
+	unsigned long start = addr;
+	unsigned long size = PAGE_SIZE*numpages;
+	unsigned long end = start + size;
+	int ret;
+
+	if (start < MODULES_VADDR || start >= MODULES_END)
+		return -EINVAL;
+
+	if (end < MODULES_VADDR || end >= MODULES_END)
+		return -EINVAL;
+
+	if (set)
+		ret = apply_to_page_range(&init_mm, start, size,
+					set_page_range, (void *)prot);
+	else
+		ret = apply_to_page_range(&init_mm, start, size,
+					clear_page_range, (void *)prot);
+
+	flush_tlb_kernel_range(start, end);
+	isb();
+	return ret;
+}
+
+static int change_memory_set_bit(unsigned long addr, int numpages,
+					pgprot_t prot)
+{
+	return change_memory_common(addr, numpages, prot, true);
+}
+
+static int change_memory_clear_bit(unsigned long addr, int numpages,
+					pgprot_t prot)
+{
+	return change_memory_common(addr, numpages, prot, false);
+}
+
+int set_memory_ro(unsigned long addr, int numpages)
+{
+	return change_memory_set_bit(addr, numpages, __pgprot(PTE_RDONLY));
+}
+EXPORT_SYMBOL_GPL(set_memory_ro);
+
+int set_memory_rw(unsigned long addr, int numpages)
+{
+	return change_memory_clear_bit(addr, numpages, __pgprot(PTE_RDONLY));
+}
+EXPORT_SYMBOL_GPL(set_memory_rw);
+
+int set_memory_nx(unsigned long addr, int numpages)
+{
+	return change_memory_set_bit(addr, numpages, __pgprot(PTE_PXN));
+}
+EXPORT_SYMBOL_GPL(set_memory_nx);
+
+int set_memory_x(unsigned long addr, int numpages)
+{
+	return change_memory_clear_bit(addr, numpages, __pgprot(PTE_PXN));
+}
+EXPORT_SYMBOL_GPL(set_memory_x);