diff mbox series

[v4,22/26] arm64: mm: move ro_after_init section into the data segment

Message ID 20220613144550.3760857-23-ardb@kernel.org (mailing list archive)
State New, archived
Headers show
Series arm64: refactor boot flow and add support for WXN | expand

Commit Message

Ard Biesheuvel June 13, 2022, 2:45 p.m. UTC
Currently, the ro_after_init sections sits right in the middle of the
text/rodata/inittext segment, making it difficult to map any of those
non-writable during early boot. So instead, move it to the start of
.data, and update the init sequences so that the section is remapped
read-only once startup completes.

Note that this moves the entire HYP data section into .data as well -
this likely needs to remain as a single block for now, but could perhaps
split into a .rodata and .data..ro_after_init section later.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 arch/arm64/kernel/vmlinux.lds.S | 42 ++++++++++++--------
 arch/arm64/mm/mmu.c             | 29 ++++++++------
 2 files changed, 42 insertions(+), 29 deletions(-)

Comments

Kees Cook June 13, 2022, 5 p.m. UTC | #1
On Mon, Jun 13, 2022 at 04:45:46PM +0200, Ard Biesheuvel wrote:
> Currently, the ro_after_init sections sits right in the middle of the
> text/rodata/inittext segment, making it difficult to map any of those
> non-writable during early boot. So instead, move it to the start of
> .data, and update the init sequences so that the section is remapped
> read-only once startup completes.
> 
> Note that this moves the entire HYP data section into .data as well -
> this likely needs to remain as a single block for now, but could perhaps
> split into a .rodata and .data..ro_after_init section later.

If I'm reading this correctly, this means that .data..ro_after_init now
lives between .data and .rodata?

Do the various LKDTM tests still pass after this change?

Reviewed-by: Kees Cook <keescook@chromium.org>
Ard Biesheuvel June 13, 2022, 5:16 p.m. UTC | #2
On Mon, 13 Jun 2022 at 19:00, Kees Cook <keescook@chromium.org> wrote:
>
> On Mon, Jun 13, 2022 at 04:45:46PM +0200, Ard Biesheuvel wrote:
> > Currently, the ro_after_init sections sits right in the middle of the
> > text/rodata/inittext segment, making it difficult to map any of those
> > non-writable during early boot. So instead, move it to the start of
> > .data, and update the init sequences so that the section is remapped
> > read-only once startup completes.
> >
> > Note that this moves the entire HYP data section into .data as well -
> > this likely needs to remain as a single block for now, but could perhaps
> > split into a .rodata and .data..ro_after_init section later.
>
> If I'm reading this correctly, this means that .data..ro_after_init now
> lives between .data and .rodata?
>

No, between .initdata and .data

> Do the various LKDTM tests still pass after this change?
>

Good question, I'll check.

> Reviewed-by: Kees Cook <keescook@chromium.org>
>
> --
> Kees Cook
Kees Cook June 13, 2022, 11:38 p.m. UTC | #3
On Mon, Jun 13, 2022 at 07:16:15PM +0200, Ard Biesheuvel wrote:
> On Mon, 13 Jun 2022 at 19:00, Kees Cook <keescook@chromium.org> wrote:
> >
> > On Mon, Jun 13, 2022 at 04:45:46PM +0200, Ard Biesheuvel wrote:
> > > Currently, the ro_after_init sections sits right in the middle of the
> > > text/rodata/inittext segment, making it difficult to map any of those
> > > non-writable during early boot. So instead, move it to the start of
> > > .data, and update the init sequences so that the section is remapped
> > > read-only once startup completes.
> > >
> > > Note that this moves the entire HYP data section into .data as well -
> > > this likely needs to remain as a single block for now, but could perhaps
> > > split into a .rodata and .data..ro_after_init section later.
> >
> > If I'm reading this correctly, this means that .data..ro_after_init now
> > lives between .data and .rodata?
> >
> 
> No, between .initdata and .data

Ah, doesn't this mean more padding (for segment alignment) used? On other
architectures .data..ro_after_init tried to be near the writable/read-only
boundary so segment padding was only needed on one side (e.g. it could
live at the end of .rodata without segment alignment but before .data
which was segment aligned.) Then when .rodata was made read-only (after
__init), .data..ro_after_init would also get set read-only.

In this case, I think it ends up needing segment alignment both at the
front and the end, since the .initdata and .data are freed and left
writable, respectively?
Ard Biesheuvel June 16, 2022, 11:31 a.m. UTC | #4
On Tue, 14 Jun 2022 at 01:38, Kees Cook <keescook@chromium.org> wrote:
>
> On Mon, Jun 13, 2022 at 07:16:15PM +0200, Ard Biesheuvel wrote:
> > On Mon, 13 Jun 2022 at 19:00, Kees Cook <keescook@chromium.org> wrote:
> > >
> > > On Mon, Jun 13, 2022 at 04:45:46PM +0200, Ard Biesheuvel wrote:
> > > > Currently, the ro_after_init sections sits right in the middle of the
> > > > text/rodata/inittext segment, making it difficult to map any of those
> > > > non-writable during early boot. So instead, move it to the start of
> > > > .data, and update the init sequences so that the section is remapped
> > > > read-only once startup completes.
> > > >
> > > > Note that this moves the entire HYP data section into .data as well -
> > > > this likely needs to remain as a single block for now, but could perhaps
> > > > split into a .rodata and .data..ro_after_init section later.
> > >
> > > If I'm reading this correctly, this means that .data..ro_after_init now
> > > lives between .data and .rodata?
> > >
> >
> > No, between .initdata and .data
>
> Ah, doesn't this mean more padding (for segment alignment) used? On other
> architectures .data..ro_after_init tried to be near the writable/read-only
> boundary so segment padding was only needed on one side (e.g. it could
> live at the end of .rodata without segment alignment but before .data
> which was segment aligned.) Then when .rodata was made read-only (after
> __init), .data..ro_after_init would also get set read-only.
>
> In this case, I think it ends up needing segment alignment both at the
> front and the end, since the .initdata and .data are freed and left
> writable, respectively?
>

We used to have

text
--
rodata
(ro_after_init)
--
inittext
--
initdata
--
data
bss

where -- are the segment boundaries, which are always aligned to 64k on arm64

After this patch, we get

text
--
rodata
--
inittext
--
initdata
--
(ro_after_init)
data
bss

so in terms of padding due to alignment, there is not a lot of difference.

The main difference here is the fact that we lose the ability to use
block mappings, but if anyone cares about that, we could work around
this by creating a separate segment for ro_after_init.
Kees Cook June 16, 2022, 4:18 p.m. UTC | #5
On Thu, Jun 16, 2022 at 01:31:23PM +0200, Ard Biesheuvel wrote:
> We used to have
> 
> text
> --
> rodata
> (ro_after_init)
> --
> inittext
> --
> initdata
> --
> data
> bss
> 
> where -- are the segment boundaries, which are always aligned to 64k on arm64
> 
> After this patch, we get
> 
> text
> --
> rodata
> --
> inittext
> --
> initdata
> --
> (ro_after_init)
> data
> bss
> 
> so in terms of padding due to alignment, there is not a lot of difference.

But how is ro_after_init read-only and data isn't, if there isn't a
segment alignment to make that work out?
Ard Biesheuvel June 16, 2022, 4:31 p.m. UTC | #6
On Thu, 16 Jun 2022 at 18:18, Kees Cook <keescook@chromium.org> wrote:
>
> On Thu, Jun 16, 2022 at 01:31:23PM +0200, Ard Biesheuvel wrote:
> > We used to have
> >
> > text
> > --
> > rodata
> > (ro_after_init)
> > --
> > inittext
> > --
> > initdata
> > --
> > data
> > bss
> >
> > where -- are the segment boundaries, which are always aligned to 64k on arm64
> >
> > After this patch, we get
> >
> > text
> > --
> > rodata
> > --
> > inittext
> > --
> > initdata
> > --
> > (ro_after_init)
> > data
> > bss
> >
> > so in terms of padding due to alignment, there is not a lot of difference.
>
> But how is ro_after_init read-only and data isn't, if there isn't a
> segment alignment to make that work out?
>

Actually, there is a segment alignment between ro_after_init and data
- my diagram is inaccurate. But we don't actually need that to remap
this slice of memory r/o
diff mbox series

Patch

diff --git a/arch/arm64/kernel/vmlinux.lds.S b/arch/arm64/kernel/vmlinux.lds.S
index 45131e354e27..736aca63dad1 100644
--- a/arch/arm64/kernel/vmlinux.lds.S
+++ b/arch/arm64/kernel/vmlinux.lds.S
@@ -59,6 +59,7 @@ 
 
 #define RO_EXCEPTION_TABLE_ALIGN	4
 #define RUNTIME_DISCARD_EXIT
+#define RO_AFTER_INIT_DATA
 
 #include <asm-generic/vmlinux.lds.h>
 #include <asm/cache.h>
@@ -188,30 +189,13 @@  SECTIONS
 	/* everything from this point to __init_begin will be marked RO NX */
 	RO_DATA(PAGE_SIZE)
 
-	HYPERVISOR_DATA_SECTIONS
-
 	/* code sections that are never executed via the kernel mapping */
 	.rodata.text : {
 		TRAMP_TEXT
 		HIBERNATE_TEXT
 		KEXEC_TEXT
-		. = ALIGN(PAGE_SIZE);
 	}
 
-	idmap_pg_dir = .;
-	. += PAGE_SIZE;
-
-#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
-	tramp_pg_dir = .;
-	. += PAGE_SIZE;
-#endif
-
-	reserved_pg_dir = .;
-	. += PAGE_SIZE;
-
-	swapper_pg_dir = .;
-	. += PAGE_SIZE;
-
 	. = ALIGN(SEGMENT_ALIGN);
 	__init_begin = .;
 	__inittext_begin = .;
@@ -274,6 +258,30 @@  SECTIONS
 
 	_data = .;
 	_sdata = .;
+
+	__start_ro_after_init = .;
+	idmap_pg_dir = .;
+	. += PAGE_SIZE;
+
+#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
+	tramp_pg_dir = .;
+	. += PAGE_SIZE;
+#endif
+	reserved_pg_dir = .;
+	. += PAGE_SIZE;
+
+	swapper_pg_dir = .;
+	. += PAGE_SIZE;
+
+	HYPERVISOR_DATA_SECTIONS
+
+	.data.ro_after_init : {
+		*(.data..ro_after_init)
+		JUMP_TABLE_DATA
+		. = ALIGN(SEGMENT_ALIGN);
+		__end_ro_after_init = .;
+	}
+
 	RW_DATA(L1_CACHE_BYTES, PAGE_SIZE, THREAD_ALIGN)
 
 	/*
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 9828ad826837..e9b074ffc768 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -495,11 +495,17 @@  static void __init __map_memblock(pgd_t *pgdp, phys_addr_t start,
 void __init mark_linear_text_alias_ro(void)
 {
 	/*
-	 * Remove the write permissions from the linear alias of .text/.rodata
+	 * Remove the write permissions from the linear alias of .text/.rodata/ro_after_init
 	 */
 	update_mapping_prot(__pa_symbol(_stext), (unsigned long)lm_alias(_stext),
 			    (unsigned long)__init_begin - (unsigned long)_stext,
 			    PAGE_KERNEL_RO);
+
+	update_mapping_prot(__pa_symbol(__start_ro_after_init),
+			    (unsigned long)lm_alias(__start_ro_after_init),
+			    (unsigned long)__end_ro_after_init -
+			    (unsigned long)__start_ro_after_init,
+			    PAGE_KERNEL_RO);
 }
 
 static bool crash_mem_map __initdata;
@@ -608,12 +614,10 @@  void mark_rodata_ro(void)
 {
 	unsigned long section_size;
 
-	/*
-	 * mark .rodata as read only. Use __init_begin rather than __end_rodata
-	 * to cover NOTES and EXCEPTION_TABLE.
-	 */
-	section_size = (unsigned long)__init_begin - (unsigned long)__start_rodata;
-	update_mapping_prot(__pa_symbol(__start_rodata), (unsigned long)__start_rodata,
+	section_size = (unsigned long)__end_ro_after_init -
+		       (unsigned long)__start_ro_after_init;
+	update_mapping_prot(__pa_symbol(__start_ro_after_init),
+			    (unsigned long)__start_ro_after_init,
 			    section_size, PAGE_KERNEL_RO);
 
 	debug_checkwx();
@@ -733,18 +737,19 @@  static void __init map_kernel(pgd_t *pgdp)
 		text_prot = __pgprot_modify(text_prot, PTE_GP, PTE_GP);
 
 	/*
-	 * Only rodata will be remapped with different permissions later on,
-	 * all other segments are allowed to use contiguous mappings.
+	 * Only data will be partially remapped with different permissions
+	 * later on, all other segments are allowed to use contiguous mappings.
 	 */
 	map_kernel_segment(pgdp, _stext, _etext, text_prot, &vmlinux_text, 0,
 			   VM_NO_GUARD);
-	map_kernel_segment(pgdp, __start_rodata, __inittext_begin, PAGE_KERNEL,
-			   &vmlinux_rodata, NO_CONT_MAPPINGS, VM_NO_GUARD);
+	map_kernel_segment(pgdp, __start_rodata, __inittext_begin, PAGE_KERNEL_RO,
+			   &vmlinux_rodata, 0, VM_NO_GUARD);
 	map_kernel_segment(pgdp, __inittext_begin, __inittext_end, text_prot,
 			   &vmlinux_inittext, 0, VM_NO_GUARD);
 	map_kernel_segment(pgdp, __initdata_begin, __initdata_end, PAGE_KERNEL,
 			   &vmlinux_initdata, 0, VM_NO_GUARD);
-	map_kernel_segment(pgdp, _data, _end, PAGE_KERNEL, &vmlinux_data, 0, 0);
+	map_kernel_segment(pgdp, _data, _end, PAGE_KERNEL, &vmlinux_data,
+			   NO_CONT_MAPPINGS | NO_BLOCK_MAPPINGS, 0);
 
 	if (!READ_ONCE(pgd_val(*pgd_offset_pgd(pgdp, FIXADDR_START)))) {
 		/*