linux-hardening.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] RFC v2 struct const ops pointers member hardening
@ 2021-07-28  6:52 Wang Zi-cheng
  2021-07-30 10:18 ` Enrico Weigelt, metux IT consult
  2021-07-31 20:50 ` Kees Cook
  0 siblings, 2 replies; 3+ messages in thread
From: Wang Zi-cheng @ 2021-07-28  6:52 UTC (permalink / raw)
  To: keescook, tycho, linux-hardening; +Cc: Wang Zi-cheng

Signed-off-by: Wang Zi-cheng <wzc@smail.nju.edu.cn>
--------------------
RFC v2 struct const ops pointers member hardening

I apologize for making mistakes in my previous submission, I fix the previous problems,
 add compile options and make tests(compile and lmbench).

1. this is a useful hardening, my opinion was wrong in the previous patch, 
because the attacker may overwrite a struct with an "struct file*" pointer,
 which point to a manufactured file struct with malicious f_op.
Hardening operation pointers CAN help.
> On the other side, kernel uses `kmem_cache_create` to alloc file/inode rather than `kmalloc`, 
> which makes it hard to exploit through heap overflow or UAF, so maybe this is not a "must" update.

2. V2 add compile option 'STRUCT_CONST_OPS_POINTER_HARDENING' in 
'Security options -> Kernel hardening options -> Struct const operation pointers hardening'

> PATCH v1
> some security sensitive kernel objects, i.e. inode, file, use 'const' to declare operations pointers, 
> and these pointers reference to the static global variables in read only data segment.

> ```
> struct file {
>   ...
> 	const struct file_operations	*f_op;
> 
> const struct file_operations ext4_file_operations = {
> ```

> However, const pointers are just compile hints with no hardware restrictions and prone to be modified by attakers at runtime.
> It would be safer to make sure struct const pointer members are not pointing to malicious payload in the slab objects(direct mapping region).
> we only need to check "open" syscall, because most file-related operations start with "open".


---
 arch/x86/include/asm/pgtable_64_types.h |  5 +++++
 fs/open.c                               |  5 +++++
 include/linux/mm.h                      |  9 +++++++++
 security/Kconfig.hardening              | 13 +++++++++++++
 4 files changed, 32 insertions(+)

diff --git a/arch/x86/include/asm/pgtable_64_types.h b/arch/x86/include/asm/pgtable_64_types.h
index 91ac10654570..869ef3dfaf29 100644
--- a/arch/x86/include/asm/pgtable_64_types.h
+++ b/arch/x86/include/asm/pgtable_64_types.h
@@ -128,18 +128,23 @@ extern unsigned int ptrs_per_p4d;
 
 #define __VMEMMAP_BASE_L4	0xffffea0000000000UL
 #define __VMEMMAP_BASE_L5	0xffd4000000000000UL
+#define DIRECT_MAPPING_TB_L4 64UL
+#define DIRECT_MAPPING_TB_L5 32000UL
 
 #ifdef CONFIG_DYNAMIC_MEMORY_LAYOUT
 # define VMALLOC_START		vmalloc_base
 # define VMALLOC_SIZE_TB	(pgtable_l5_enabled() ? VMALLOC_SIZE_TB_L5 : VMALLOC_SIZE_TB_L4)
 # define VMEMMAP_START		vmemmap_base
+# define DIRECT_MAPPING_SIZE_TB 	(pgtable_l5_enabled() ? DIRECT_MAPPING_TB_L5 : DIRECT_MAPPING_TB_L4)
 #else
 # define VMALLOC_START		__VMALLOC_BASE_L4
 # define VMALLOC_SIZE_TB	VMALLOC_SIZE_TB_L4
 # define VMEMMAP_START		__VMEMMAP_BASE_L4
+# define DIRECT_MAPPING_SIZE_TB		DIRECT_MAPPING_TB_L4
 #endif /* CONFIG_DYNAMIC_MEMORY_LAYOUT */
 
 #define VMALLOC_END		(VMALLOC_START + (VMALLOC_SIZE_TB << 40) - 1)
+#define DIRECT_MAPPING_END		(__PAGE_OFFSET + (DIRECT_MAPPING_SIZE_TB << 40) - 1)
 
 #define MODULES_VADDR		(__START_KERNEL_map + KERNEL_IMAGE_SIZE)
 /* The module sections ends with the start of the fixmap */
diff --git a/fs/open.c b/fs/open.c
index e53af13b5835..871750b57267 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -820,6 +820,11 @@ static int do_dentry_open(struct file *f,
 
 	/* normally all 3 are set; ->open() can clear them if needed */
 	f->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
+	/* check if f_op point to malicious payload */
+#ifdef CONFIG_STRUCT_CONST_OPS_POINTER_HARDENING
+	WARN_ON (!check_valid_ops_pointer((unsigned long) f->f_op));
+	WARN_ON (!check_valid_ops_pointer((unsigned long) f->f_inode->i_op));
+#endif
 	if (!open)
 		open = f->f_op->open;
 	if (open) {
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 8ae31622deef..c787acda7012 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3250,6 +3250,15 @@ static inline int seal_check_future_write(int seals, struct vm_area_struct *vma)
 
 	return 0;
 }
+#ifdef CONFIG_STRUCT_CONST_OPS_POINTER_HARDENING
+static inline bool check_valid_ops_pointer(unsigned long addr)
+{
+	if (addr >= __PAGE_OFFSET && addr <= DIRECT_MAPPING_END) 
+		return false;
+	else
+		return true;
+}
+#endif
 
 #endif /* __KERNEL__ */
 #endif /* _LINUX_MM_H */
diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening
index a56c36470cb1..3a7e2a046842 100644
--- a/security/Kconfig.hardening
+++ b/security/Kconfig.hardening
@@ -219,4 +219,17 @@ config INIT_ON_FREE_DEFAULT_ON
 
 endmenu
 
+config STRUCT_CONST_OPS_POINTER_HARDENING
+	depends on X86_64
+	bool "Struct const operation pointers hardening"
+	help
+		Security sensitive kernel objects, i.e. 'inode', 'file' protect 
+		indirect call pointers by declaring const operation pointers and 
+		making these pointers reference to static const global variables. 
+		However const members in the kernel object are just compile hints
+		with no hardware restriction. To hardening the operations pointers
+		in structs, put a check in "open syscall" and make sure pointers
+		are not pointing to the malicious payload in slub 
+		objects(direct mapping region).
+
 endmenu
-- 
2.32.0




^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH] RFC v2 struct const ops pointers member hardening
  2021-07-28  6:52 [PATCH] RFC v2 struct const ops pointers member hardening Wang Zi-cheng
@ 2021-07-30 10:18 ` Enrico Weigelt, metux IT consult
  2021-07-31 20:50 ` Kees Cook
  1 sibling, 0 replies; 3+ messages in thread
From: Enrico Weigelt, metux IT consult @ 2021-07-30 10:18 UTC (permalink / raw)
  To: Wang Zi-cheng, keescook, tycho, linux-hardening

On 28.07.21 08:52, Wang Zi-cheng wrote:

Hi,

> 1. this is a useful hardening, my opinion was wrong in the previous patch,
> because the attacker may overwrite a struct with an "struct file*" pointer,
>   which point to a manufactured file struct with malicious f_op.
> Hardening operation pointers CAN help.
>> On the other side, kernel uses `kmem_cache_create` to alloc file/inode rather than `kmalloc`,
>> which makes it hard to exploit through heap overflow or UAF, so maybe this is not a "must" update.

note that there can be situations where a file_operations struct is
actually created at runtime. quite rare, but still possible.


--mtx

-- 
---
Hinweis: unverschlüsselte E-Mails können leicht abgehört und manipuliert
werden ! Für eine vertrauliche Kommunikation senden Sie bitte ihren
GPG/PGP-Schlüssel zu.
---
Enrico Weigelt, metux IT consult
Free software and Linux embedded engineering
info@metux.net -- +49-151-27565287

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] RFC v2 struct const ops pointers member hardening
  2021-07-28  6:52 [PATCH] RFC v2 struct const ops pointers member hardening Wang Zi-cheng
  2021-07-30 10:18 ` Enrico Weigelt, metux IT consult
@ 2021-07-31 20:50 ` Kees Cook
  1 sibling, 0 replies; 3+ messages in thread
From: Kees Cook @ 2021-07-31 20:50 UTC (permalink / raw)
  To: Wang Zi-cheng; +Cc: tycho, linux-hardening

On Wed, Jul 28, 2021 at 02:52:40PM +0800, Wang Zi-cheng wrote:
> Signed-off-by: Wang Zi-cheng <wzc@smail.nju.edu.cn>
> --------------------
> RFC v2 struct const ops pointers member hardening
> 
> I apologize for making mistakes in my previous submission, I fix the previous problems,
>  add compile options and make tests(compile and lmbench).
> 
> 1. this is a useful hardening, my opinion was wrong in the previous patch, 
> because the attacker may overwrite a struct with an "struct file*" pointer,
>  which point to a manufactured file struct with malicious f_op.
> Hardening operation pointers CAN help.
> > On the other side, kernel uses `kmem_cache_create` to alloc file/inode rather than `kmalloc`, 
> > which makes it hard to exploit through heap overflow or UAF, so maybe this is not a "must" update.
> 
> 2. V2 add compile option 'STRUCT_CONST_OPS_POINTER_HARDENING' in 
> 'Security options -> Kernel hardening options -> Struct const operation pointers hardening'
> 
> > PATCH v1
> > some security sensitive kernel objects, i.e. inode, file, use 'const' to declare operations pointers, 
> > and these pointers reference to the static global variables in read only data segment.
> 
> > ```
> > struct file {
> >   ...
> > 	const struct file_operations	*f_op;
> > 
> > const struct file_operations ext4_file_operations = {
> > ```
> 
> > However, const pointers are just compile hints with no hardware restrictions and prone to be modified by attakers at runtime.
> > It would be safer to make sure struct const pointer members are not pointing to malicious payload in the slab objects(direct mapping region).
> > we only need to check "open" syscall, because most file-related operations start with "open".
> 
> 
> ---
>  arch/x86/include/asm/pgtable_64_types.h |  5 +++++
>  fs/open.c                               |  5 +++++
>  include/linux/mm.h                      |  9 +++++++++
>  security/Kconfig.hardening              | 13 +++++++++++++
>  4 files changed, 32 insertions(+)
> 
> diff --git a/arch/x86/include/asm/pgtable_64_types.h b/arch/x86/include/asm/pgtable_64_types.h
> index 91ac10654570..869ef3dfaf29 100644
> --- a/arch/x86/include/asm/pgtable_64_types.h
> +++ b/arch/x86/include/asm/pgtable_64_types.h
> @@ -128,18 +128,23 @@ extern unsigned int ptrs_per_p4d;
>  
>  #define __VMEMMAP_BASE_L4	0xffffea0000000000UL
>  #define __VMEMMAP_BASE_L5	0xffd4000000000000UL
> +#define DIRECT_MAPPING_TB_L4 64UL
> +#define DIRECT_MAPPING_TB_L5 32000UL
>  
>  #ifdef CONFIG_DYNAMIC_MEMORY_LAYOUT
>  # define VMALLOC_START		vmalloc_base
>  # define VMALLOC_SIZE_TB	(pgtable_l5_enabled() ? VMALLOC_SIZE_TB_L5 : VMALLOC_SIZE_TB_L4)
>  # define VMEMMAP_START		vmemmap_base
> +# define DIRECT_MAPPING_SIZE_TB 	(pgtable_l5_enabled() ? DIRECT_MAPPING_TB_L5 : DIRECT_MAPPING_TB_L4)
>  #else
>  # define VMALLOC_START		__VMALLOC_BASE_L4
>  # define VMALLOC_SIZE_TB	VMALLOC_SIZE_TB_L4
>  # define VMEMMAP_START		__VMEMMAP_BASE_L4
> +# define DIRECT_MAPPING_SIZE_TB		DIRECT_MAPPING_TB_L4
>  #endif /* CONFIG_DYNAMIC_MEMORY_LAYOUT */
>  
>  #define VMALLOC_END		(VMALLOC_START + (VMALLOC_SIZE_TB << 40) - 1)
> +#define DIRECT_MAPPING_END		(__PAGE_OFFSET + (DIRECT_MAPPING_SIZE_TB << 40) - 1)
>  
>  #define MODULES_VADDR		(__START_KERNEL_map + KERNEL_IMAGE_SIZE)
>  /* The module sections ends with the start of the fixmap */
> diff --git a/fs/open.c b/fs/open.c
> index e53af13b5835..871750b57267 100644
> --- a/fs/open.c
> +++ b/fs/open.c
> @@ -820,6 +820,11 @@ static int do_dentry_open(struct file *f,
>  
>  	/* normally all 3 are set; ->open() can clear them if needed */
>  	f->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
> +	/* check if f_op point to malicious payload */
> +#ifdef CONFIG_STRUCT_CONST_OPS_POINTER_HARDENING
> +	WARN_ON (!check_valid_ops_pointer((unsigned long) f->f_op));
> +	WARN_ON (!check_valid_ops_pointer((unsigned long) f->f_inode->i_op));

I would use WARN_ON_ONCE() to avoid spamming the logs. Also, if you use
a void * argument for check_valid_ops_pointer(), you can avoid these
casts, and keep the cast central to check_valid_ops_pointer() itself,
making the code easier to read.

Also, I recommend using:

	if (IS_ENABLED(CONFIG_STRUCT_CONST_OPS_POINTER_HARDENING)) {
	}

instead of the #ifdefs.

> +#endif
>  	if (!open)
>  		open = f->f_op->open;

Shouldn't the test happen here instead, since the "open != NULL" case
will skip dereferencing f->f_op?

>  	if (open) {
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 8ae31622deef..c787acda7012 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -3250,6 +3250,15 @@ static inline int seal_check_future_write(int seals, struct vm_area_struct *vma)
>  
>  	return 0;
>  }
> +#ifdef CONFIG_STRUCT_CONST_OPS_POINTER_HARDENING
> +static inline bool check_valid_ops_pointer(unsigned long addr)
> +{
> +	if (addr >= __PAGE_OFFSET && addr <= DIRECT_MAPPING_END) 
> +		return false;
> +	else
> +		return true;
> +}
> +#endif

It looks like you want to be checking for the ops pointer to point
into kernel .rodata section, yes? (Or maybe some are also in just
.data?) I think an easier (and more accurate) approach would be to
introduce something like the existing func_ptr_is_kernel_text() routine
but for rodata (maybe named func_ptr_is_kernel_rodata()). You'd
need to add core_kernel_rodata() like core_kernel_data() and
is_module_rodata_address(), similar to is_module_text_address().

>  
>  #endif /* __KERNEL__ */
>  #endif /* _LINUX_MM_H */
> diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening
> index a56c36470cb1..3a7e2a046842 100644
> --- a/security/Kconfig.hardening
> +++ b/security/Kconfig.hardening
> @@ -219,4 +219,17 @@ config INIT_ON_FREE_DEFAULT_ON
>  
>  endmenu
>  
> +config STRUCT_CONST_OPS_POINTER_HARDENING
> +	depends on X86_64

Using ptr_is_kernel_rodata() would also make this architecture-agnostic.

> +	bool "Struct const operation pointers hardening"
> +	help
> +		Security sensitive kernel objects, i.e. 'inode', 'file' protect 
> +		indirect call pointers by declaring const operation pointers and 
> +		making these pointers reference to static const global variables. 
> +		However const members in the kernel object are just compile hints
> +		with no hardware restriction. To hardening the operations pointers
> +		in structs, put a check in "open syscall" and make sure pointers
> +		are not pointing to the malicious payload in slub 
> +		objects(direct mapping region).

This appears to be a form of weak CFI designed to protect specifically
these ops pointers, yes?

For your commit log, perhaps justify why ops pointers are chosen to
protect. Are there other pointers that could be protected as well? (In
other words, what would be the _next_ most common thing an attacker
would corrupt in the face of this defense being present?)

-Kees

> +
>  endmenu
> -- 
> 2.32.0
> 
> 
> 

-- 
Kees Cook

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2021-07-31 20:51 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-28  6:52 [PATCH] RFC v2 struct const ops pointers member hardening Wang Zi-cheng
2021-07-30 10:18 ` Enrico Weigelt, metux IT consult
2021-07-31 20:50 ` Kees Cook

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).