PPC64/IA64/PARISC have function descriptors. LKDTM doesn't work
on those three architectures because LKDTM messes up function
descriptors with functions.
This series does some cleanup in the three architectures and
refactors function descriptors so that it can then easily use it
in a generic way in LKDTM.
Patch 8 is not absolutely necessary but it is a good trivial cleanup.
Changes in v3:
- Addressed received comments
- Swapped some of the powerpc patches to keep func_descr_t renamed as struct func_desc and remove 'struct ppc64_opd_entry'
- Changed HAVE_FUNCTION_DESCRIPTORS macro to a config item CONFIG_HAVE_FUNCTION_DESCRIPTORS
- Dropped patch 11 ("Fix lkdtm_EXEC_RODATA()")
Changes in v2:
- Addressed received comments
- Moved dereference_[kernel]_function_descriptor() out of line
- Added patches to remove func_descr_t and func_desc_t in powerpc
- Using func_desc_t instead of funct_descr_t
- Renamed HAVE_DEREFERENCE_FUNCTION_DESCRIPTOR to HAVE_FUNCTION_DESCRIPTORS
- Added a new lkdtm test to check protection of function descriptors
Christophe Leroy (12):
powerpc: Move and rename func_descr_t
powerpc: Use 'struct func_desc' instead of 'struct ppc64_opd_entry'
powerpc: Remove 'struct ppc64_opd_entry'
powerpc: Prepare func_desc_t for refactorisation
ia64: Rename 'ip' to 'addr' in 'struct fdesc'
asm-generic: Define CONFIG_HAVE_FUNCTION_DESCRIPTORS
asm-generic: Define 'func_desc_t' to commonly describe function
descriptors
asm-generic: Refactor dereference_[kernel]_function_descriptor()
lkdtm: Force do_nothing() out of line
lkdtm: Really write into kernel text in WRITE_KERN
lkdtm: Fix execute_[user]_location()
lkdtm: Add a test for function descriptors protection
arch/Kconfig | 3 +
arch/ia64/Kconfig | 1 +
arch/ia64/include/asm/elf.h | 2 +-
arch/ia64/include/asm/sections.h | 24 +-------
arch/ia64/kernel/module.c | 6 +-
arch/parisc/Kconfig | 1 +
arch/parisc/include/asm/sections.h | 16 ++----
arch/parisc/kernel/process.c | 21 -------
arch/powerpc/Kconfig | 1 +
arch/powerpc/include/asm/code-patching.h | 2 +-
arch/powerpc/include/asm/elf.h | 6 ++
arch/powerpc/include/asm/sections.h | 29 ++--------
arch/powerpc/include/asm/types.h | 6 --
arch/powerpc/include/uapi/asm/elf.h | 8 ---
arch/powerpc/kernel/module_64.c | 38 +++++--------
arch/powerpc/kernel/ptrace/ptrace.c | 6 ++
arch/powerpc/kernel/signal_64.c | 8 +--
drivers/misc/lkdtm/core.c | 1 +
drivers/misc/lkdtm/lkdtm.h | 1 +
drivers/misc/lkdtm/perms.c | 71 +++++++++++++++++++-----
include/asm-generic/sections.h | 13 ++++-
include/linux/kallsyms.h | 2 +-
kernel/extable.c | 23 +++++++-
23 files changed, 146 insertions(+), 143 deletions(-)
--
2.31.1
There are three architectures with function descriptors, try to
have common names for the address they contain in order to
refactor some functions into generic functions later.
powerpc has 'entry'
ia64 has 'ip'
parisc has 'addr'
Vote for 'addr' and update 'func_descr_t' accordingly.
Move it in asm/elf.h to have it at the same place on all
three architectures, remove the typedef which hides its real
type, and change it to a smoother name 'struct func_desc'.
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/include/asm/code-patching.h | 2 +-
arch/powerpc/include/asm/elf.h | 6 ++++++
arch/powerpc/include/asm/types.h | 6 ------
arch/powerpc/kernel/signal_64.c | 8 ++++----
4 files changed, 11 insertions(+), 11 deletions(-)
@@ -933,11 +933,11 @@ int handle_rt_signal64(struct ksignal *ksig, sigset_t *set,*descriptoristheentryaddressofsignalandthesecond*entryistheTOCvalueweneedtouse.*/-func_descr_t__user*funct_desc_ptr=-(func_descr_t__user*)ksig->ka.sa.sa_handler;+structfunc_desc__user*ptr=+(structfunc_desc__user*)ksig->ka.sa.sa_handler;-err|=get_user(regs->ctr,&funct_desc_ptr->entry);-err|=get_user(regs->gpr[2],&funct_desc_ptr->toc);+err|=get_user(regs->ctr,&ptr->addr);+err|=get_user(regs->gpr[2],&ptr->toc);}/* enter the signal handler in native-endian mode */
WRITE_KERN is supposed to overwrite some kernel text, namely
do_overwritten() function.
But at the time being it overwrites do_overwritten() function
descriptor, not function text.
Fix it by dereferencing the function descriptor to obtain
function text pointer.
And make do_overwritten() noinline so that it is really
do_overwritten() which is called by lkdtm_WRITE_KERN().
Acked-by: Kees Cook <redacted>
Signed-off-by: Christophe Leroy <redacted>
---
drivers/misc/lkdtm/perms.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
@@ -10,6 +10,7 @@#include<linux/mman.h>#include<linux/uaccess.h>#include<asm/cacheflush.h>+#include<asm/sections.h>/* Whether or not to fill the target memory area with do_nothing(). */#define CODE_WRITE true
@@ -37,7 +38,7 @@ static noinline void do_nothing(void)}/* Must immediately follow do_nothing for size calculuations to work out. */-staticvoiddo_overwritten(void)+staticnoinlinevoiddo_overwritten(void){pr_info("do_overwritten wasn't overwritten!\n");return;
@@ -113,8 +114,9 @@ void lkdtm_WRITE_KERN(void)size_tsize;volatileunsignedchar*ptr;-size=(unsignedlong)do_overwritten-(unsignedlong)do_nothing;-ptr=(unsignedchar*)do_overwritten;+size=(unsignedlong)dereference_function_descriptor(do_overwritten)-+(unsignedlong)dereference_function_descriptor(do_nothing);+ptr=dereference_function_descriptor(do_overwritten);pr_info("attempting bad %zu byte write at %px\n",size,ptr);memcpy((void*)ptr,(unsignedchar*)do_nothing,size);
LKDTM tests display that the run do_nothing() at a given
address, but in reality do_nothing() is inlined into the
caller.
Force it out of line so that it really runs text at the
displayed address.
Acked-by: Kees Cook <redacted>
Signed-off-by: Christophe Leroy <redacted>
---
drivers/misc/lkdtm/perms.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
@@ -21,7 +21,7 @@/* This is non-const, so it will end up in the .data section. */staticu8data_area[EXEC_SIZE];-/* This is cost, so it will end up in the .rodata section. */+/* This is const, so it will end up in the .rodata section. */staticconstunsignedlongrodata=0xAA55AA55;/* This is marked __ro_after_init, so it should ultimately be .rodata. */
'struct ppc64_opd_entry' doesn't belong to uapi/asm/elf.h
It was initially in module_64.c and commit 2d291e902791 ("Fix compile
failure with non modular builds") moved it into asm/elf.h
But it was by mistake added outside of __KERNEL__ section,
therefore commit c3617f72036c ("UAPI: (Scripted) Disintegrate
arch/powerpc/include/asm") moved it to uapi/asm/elf.h
Now that it is not used anymore by the kernel, remove it.
Reviewed-by: Kees Cook <redacted>
Reviewed-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/include/uapi/asm/elf.h | 8 --------
1 file changed, 8 deletions(-)
@@ -289,12 +289,4 @@ typedef elf_fpreg_t elf_vsrreghalf_t32[ELF_NVSRHALFREG];/* Keep this the last entry. */#define R_PPC64_NUM 253-/* There's actually a third entry here, but it's unused */-structppc64_opd_entry-{-unsignedlongfuncaddr;-unsignedlongr2;-};--#endif /* _UAPI_ASM_POWERPC_ELF_H */
'struct ppc64_opd_entry' is somehow redundant with 'struct func_desc',
the later is more correct/complete as it includes the third
field which is unused.
So use 'struct func_desc' instead of 'struct ppc64_opd_entry'
Reviewed-by: Kees Cook <redacted>
Reviewed-by: Daniel Axtens <redacted>
Reviewed-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/include/asm/sections.h | 4 ++--
arch/powerpc/kernel/module_64.c | 10 +++++-----
2 files changed, 7 insertions(+), 7 deletions(-)
@@ -74,10 +74,10 @@ static inline int overlaps_kernel_text(unsigned long start, unsigned long end)#undef dereference_function_descriptorstaticinlinevoid*dereference_function_descriptor(void*ptr){-structppc64_opd_entry*desc=ptr;+structfunc_desc*desc=ptr;void*p;-if(!get_kernel_nofault(p,(void*)&desc->funcaddr))+if(!get_kernel_nofault(p,(void*)&desc->addr))ptr=p;returnptr;}
@@ -64,19 +64,19 @@ static unsigned int local_entry_offset(const Elf64_Sym *sym)#else/* An address is address of the OPD entry, which contains address of fn. */-typedefstructppc64_opd_entryfunc_desc_t;+typedefstructfunc_descfunc_desc_t;staticfunc_desc_tfunc_desc(unsignedlongaddr){-return*(structppc64_opd_entry*)addr;+return*(structfunc_desc*)addr;}staticunsignedlongfunc_addr(unsignedlongaddr){-returnfunc_desc(addr).funcaddr;+returnfunc_desc(addr).addr;}staticunsignedlongstub_func_addr(func_desc_tfunc){-returnfunc.funcaddr;+returnfunc.addr;}staticunsignedintlocal_entry_offset(constElf64_Sym*sym){
@@ -187,7 +187,7 @@ static int relacmp(const void *_x, const void *_y)staticunsignedlongget_stubs_size(constElf64_Ehdr*hdr,constElf64_Shdr*sechdrs){-/* One extra reloc so it's always 0-funcaddr terminated */+/* One extra reloc so it's always 0-addr terminated */unsignedlongrelocs=1;unsignedi;
There are three architectures with function descriptors, try to
have common names for the address they contain in order to
refactor some functions into generic functions later.
powerpc has 'entry'
ia64 has 'ip'
parisc has 'addr'
Vote for 'addr' and update 'struct fdesc' accordingly.
Reviewed-by: Kees Cook <redacted>
Signed-off-by: Christophe Leroy <redacted>
---
arch/ia64/include/asm/elf.h | 2 +-
arch/ia64/include/asm/sections.h | 2 +-
arch/ia64/kernel/module.c | 6 +++---
3 files changed, 5 insertions(+), 5 deletions(-)
@@ -602,15 +602,15 @@ get_fdesc (struct module *mod, uint64_t value, int *okp)returnvalue;/* Look for existing function descriptor. */-while(fdesc->ip){-if(fdesc->ip==value)+while(fdesc->addr){+if(fdesc->addr==value)return(uint64_t)fdesc;if((uint64_t)++fdesc>=mod->arch.opd->sh_addr+mod->arch.opd->sh_size)BUG();}/* Create new one */-fdesc->ip=value;+fdesc->addr=value;fdesc->gp=mod->arch.gp;return(uint64_t)fdesc;}
dereference_function_descriptor() and
dereference_kernel_function_descriptor() are identical on the
three architectures implementing them.
Make them common and put them out-of-line in kernel/extable.c
which is one of the users and has similar type of functions.
Reviewed-by: Kees Cook <redacted>
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Christophe Leroy <redacted>
---
arch/ia64/include/asm/sections.h | 19 -------------------
arch/parisc/include/asm/sections.h | 9 ---------
arch/parisc/kernel/process.c | 21 ---------------------
arch/powerpc/include/asm/sections.h | 23 -----------------------
include/asm-generic/sections.h | 2 ++
kernel/extable.c | 23 ++++++++++++++++++++++-
6 files changed, 24 insertions(+), 73 deletions(-)
@@ -159,12 +160,32 @@ int kernel_text_address(unsigned long addr)}/*-*Onsomearchitectures(PPC64,IA64)functionpointers+*Onsomearchitectures(PPC64,IA64,PARISC)functionpointers*areactuallyonlytokenstosomedatathatthenholdsthe*realfunctionaddress.Asaresult,tofindifafunction*pointerispartofthekerneltext,weneedtodosome*specialdereferencingfirst.*/+#ifdef CONFIG_HAVE_FUNCTION_DESCRIPTORS+void*dereference_function_descriptor(void*ptr)+{+func_desc_t*desc=ptr;+void*p;++if(!get_kernel_nofault(p,(void*)&desc->addr))+ptr=p;+returnptr;+}++void*dereference_kernel_function_descriptor(void*ptr)+{+if(ptr<(void*)__start_opd||ptr>=(void*)__end_opd)+returnptr;++returndereference_function_descriptor(ptr);+}+#endif+intfunc_ptr_is_kernel_text(void*ptr){unsignedlongaddr;
Add WRITE_OPD to check that you can't modify function
descriptors.
Gives the following result when function descriptors are
not protected:
lkdtm: Performing direct entry WRITE_OPD
lkdtm: attempting bad 16 bytes write at c00000000269b358
lkdtm: FAIL: survived bad write
lkdtm: do_nothing was hijacked!
Looks like a standard compiler barrier() is not enough to force
GCC to use the modified function descriptor. Had to add a fake empty
inline assembly to force GCC to reload the function descriptor.
Signed-off-by: Christophe Leroy <redacted>
---
drivers/misc/lkdtm/core.c | 1 +
drivers/misc/lkdtm/lkdtm.h | 1 +
drivers/misc/lkdtm/perms.c | 22 ++++++++++++++++++++++
3 files changed, 24 insertions(+)
@@ -44,6 +44,11 @@ static noinline void do_overwritten(void)return;}+staticnoinlinevoiddo_almost_nothing(void)+{+pr_info("do_nothing was hijacked!\n");+}+staticvoid*setup_function_descriptor(func_desc_t*fdesc,void*dst){if(!have_function_descriptors())
@@ -144,6 +149,23 @@ void lkdtm_WRITE_KERN(void)do_overwritten();}+voidlkdtm_WRITE_OPD(void)+{+size_tsize=sizeof(func_desc_t);+void(*func)(void)=do_nothing;++if(!have_function_descriptors()){+pr_info("XFAIL: Platform doesn't use function descriptors.\n");+return;+}+pr_info("attempting bad %zu bytes write at %px\n",size,do_nothing);+memcpy(do_nothing,do_almost_nothing,size);+pr_err("FAIL: survived bad write\n");++asm("":"=m"(func));+func();+}+voidlkdtm_EXEC_DATA(void){execute_location(data_area,CODE_WRITE);
In preparation of making func_desc_t generic, change the ELFv2
version to a struct containing 'addr' element.
This allows using single helpers common to ELFv1 and ELFv2.
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/kernel/module_64.c | 32 ++++++++++++++------------------
1 file changed, 14 insertions(+), 18 deletions(-)
@@ -33,19 +33,13 @@#ifdef PPC64_ELF_ABI_v2/* An address is simply the address of the function. */-typedefunsignedlongfunc_desc_t;+typedefstruct{+unsignedlongaddr;+}func_desc_t;staticfunc_desc_tfunc_desc(unsignedlongaddr){-returnaddr;-}-staticunsignedlongfunc_addr(unsignedlongaddr)-{-returnaddr;-}-staticunsignedlongstub_func_addr(func_desc_tfunc)-{-returnfunc;+return(func_desc_t){addr};}/* PowerPC64 specific values for the Elf64_Sym st_other field. */
@@ -70,14 +64,6 @@ static func_desc_t func_desc(unsigned long addr){return*(structfunc_desc*)addr;}-staticunsignedlongfunc_addr(unsignedlongaddr)-{-returnfunc_desc(addr).addr;-}-staticunsignedlongstub_func_addr(func_desc_tfunc)-{-returnfunc.addr;-}staticunsignedintlocal_entry_offset(constElf64_Sym*sym){return0;
@@ -93,6 +79,16 @@ void *dereference_module_function_descriptor(struct module *mod, void *ptr)}#endif+staticunsignedlongfunc_addr(unsignedlongaddr)+{+returnfunc_desc(addr).addr;+}++staticunsignedlongstub_func_addr(func_desc_tfunc)+{+returnfunc.addr;+}+#define STUB_MAGIC 0x73747562 /* stub *//* Like PPC32, we need little trampolines to do > 24-bit jumps (into
Replace HAVE_DEREFERENCE_FUNCTION_DESCRIPTOR by a config option
named CONFIG_HAVE_FUNCTION_DESCRIPTORS and use it instead of
'dereference_function_descriptor' macro to know whether an
arch has function descriptors.
To limit churn in one of the following patches, use
an #ifdef/#else construct with empty first part
instead of an #ifndef in asm-generic/sections.h
On powerpc, make sure the config option matches the ABI used
by the compiler with a BUILD_BUG_ON().
And include a helper to check whether an arch has function
descriptors or not : have_function_descriptors()
Reviewed-by: Kees Cook <redacted>
Reviewed-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Christophe Leroy <redacted>
---
arch/Kconfig | 3 +++
arch/ia64/Kconfig | 1 +
arch/ia64/include/asm/sections.h | 2 --
arch/parisc/Kconfig | 1 +
arch/parisc/include/asm/sections.h | 2 --
arch/powerpc/Kconfig | 1 +
arch/powerpc/include/asm/sections.h | 2 --
arch/powerpc/kernel/ptrace/ptrace.c | 6 ++++++
include/asm-generic/sections.h | 8 +++++++-
include/linux/kallsyms.h | 2 +-
10 files changed, 20 insertions(+), 8 deletions(-)
We have three architectures using function descriptors, each with its
own type and name.
Add a common typedef that can be used in generic code.
Also add a stub typedef for architecture without function descriptors,
to avoid a forest of #ifdefs.
It replaces the similar 'func_desc_t' previously defined in
arch/powerpc/kernel/module_64.c
Reviewed-by: Kees Cook <redacted>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Christophe Leroy <redacted>
---
arch/ia64/include/asm/sections.h | 3 +++
arch/parisc/include/asm/sections.h | 5 +++++
arch/powerpc/include/asm/sections.h | 4 ++++
arch/powerpc/kernel/module_64.c | 8 --------
include/asm-generic/sections.h | 3 +++
5 files changed, 15 insertions(+), 8 deletions(-)
@@ -2,6 +2,11 @@#ifndef _PARISC_SECTIONS_H#define _PARISC_SECTIONS_H+#ifdef CONFIG_HAVE_FUNCTION_DESCRIPTORS+#include<asm/elf.h>+typedefElf64_Fdescfunc_desc_t;+#endif+/* nothing to see, move along */#include<asm-generic/sections.h>
@@ -32,11 +32,6 @@#ifdef PPC64_ELF_ABI_v2-/* An address is simply the address of the function. */-typedefstruct{-unsignedlongaddr;-}func_desc_t;-staticfunc_desc_tfunc_desc(unsignedlongaddr){return(func_desc_t){addr};
@@ -57,9 +52,6 @@ static unsigned int local_entry_offset(const Elf64_Sym *sym)}#else-/* An address is address of the OPD entry, which contains address of fn. */-typedefstructfunc_descfunc_desc_t;-staticfunc_desc_tfunc_desc(unsignedlongaddr){return*(structfunc_desc*)addr;
execute_location() and execute_user_location() intent
to copy do_nothing() text and execute it at a new location.
However, at the time being it doesn't copy do_nothing() function
but do_nothing() function descriptor which still points to the
original text. So at the end it still executes do_nothing() at
its original location allthough using a copied function descriptor.
So, fix that by really copying do_nothing() text and build a new
function descriptor by copying do_nothing() function descriptor and
updating the target address with the new location.
Also fix the displayed addresses by dereferencing do_nothing()
function descriptor.
Signed-off-by: Christophe Leroy <redacted>
---
drivers/misc/lkdtm/perms.c | 37 ++++++++++++++++++++++++++++---------
1 file changed, 28 insertions(+), 9 deletions(-)
@@ -44,19 +44,34 @@ static noinline void do_overwritten(void)return;}+staticvoid*setup_function_descriptor(func_desc_t*fdesc,void*dst)+{+if(!have_function_descriptors())+returndst;++memcpy(fdesc,do_nothing,sizeof(*fdesc));+fdesc->addr=(unsignedlong)dst;+barrier();++returnfdesc;+}+staticnoinlinevoidexecute_location(void*dst,boolwrite){-void(*func)(void)=dst;+void(*func)(void);+func_desc_tfdesc;+void*do_nothing_text=dereference_function_descriptor(do_nothing);-pr_info("attempting ok execution at %px\n",do_nothing);+pr_info("attempting ok execution at %px\n",do_nothing_text);do_nothing();if(write==CODE_WRITE){-memcpy(dst,do_nothing,EXEC_SIZE);+memcpy(dst,do_nothing_text,EXEC_SIZE);flush_icache_range((unsignedlong)dst,(unsignedlong)dst+EXEC_SIZE);}-pr_info("attempting bad execution at %px\n",func);+pr_info("attempting bad execution at %px\n",dst);+func=setup_function_descriptor(&fdesc,dst);func();pr_err("FAIL: func returned\n");}
@@ -66,16 +81,19 @@ static void execute_user_location(void *dst)intcopied;/* Intentionally crossing kernel/user memory boundary. */-void(*func)(void)=dst;+void(*func)(void);+func_desc_tfdesc;+void*do_nothing_text=dereference_function_descriptor(do_nothing);-pr_info("attempting ok execution at %px\n",do_nothing);+pr_info("attempting ok execution at %px\n",do_nothing_text);do_nothing();-copied=access_process_vm(current,(unsignedlong)dst,do_nothing,+copied=access_process_vm(current,(unsignedlong)dst,do_nothing_text,EXEC_SIZE,FOLL_WRITE);if(copied<EXEC_SIZE)return;-pr_info("attempting bad execution at %px\n",func);+pr_info("attempting bad execution at %px\n",dst);+func=setup_function_descriptor(&fdesc,dst);func();pr_err("FAIL: func returned\n");}
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-10-18 05:58:32
Excerpts from Christophe Leroy's message of October 17, 2021 10:38 pm:
There are three architectures with function descriptors, try to
have common names for the address they contain in order to
refactor some functions into generic functions later.
powerpc has 'entry'
ia64 has 'ip'
parisc has 'addr'
Vote for 'addr' and update 'func_descr_t' accordingly.
Move it in asm/elf.h to have it at the same place on all
three architectures, remove the typedef which hides its real
type, and change it to a smoother name 'struct func_desc'.
@@ -933,11 +933,11 @@ int handle_rt_signal64(struct ksignal *ksig, sigset_t *set,*descriptoristheentryaddressofsignalandthesecond*entryistheTOCvalueweneedtouse.*/-func_descr_t__user*funct_desc_ptr=-(func_descr_t__user*)ksig->ka.sa.sa_handler;+structfunc_desc__user*ptr=+(structfunc_desc__user*)ksig->ka.sa.sa_handler;-err|=get_user(regs->ctr,&funct_desc_ptr->entry);-err|=get_user(regs->gpr[2],&funct_desc_ptr->toc);+err|=get_user(regs->ctr,&ptr->addr);+err|=get_user(regs->gpr[2],&ptr->toc);}/* enter the signal handler in native-endian mode */
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-10-18 06:27:36
Excerpts from Christophe Leroy's message of October 17, 2021 10:38 pm:
In preparation of making func_desc_t generic, change the ELFv2
version to a struct containing 'addr' element.
This allows using single helpers common to ELFv1 and ELFv2.
Signed-off-by: Christophe Leroy <redacted>
@@ -33,19 +33,13 @@#ifdef PPC64_ELF_ABI_v2/* An address is simply the address of the function. */-typedefunsignedlongfunc_desc_t;+typedefstruct{+unsignedlongaddr;+}func_desc_t;
I'm not quite following why this change is done. I guess it is so you
can move this func_desc_t type into core code, but why do that? Is it
just to avoid using the preprocessor?
On its own this patch looks okay.
Acked-by: Nicholas Piggin <npiggin@gmail.com>
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-10-18 06:30:08
Excerpts from Christophe Leroy's message of October 17, 2021 10:38 pm:
We have three architectures using function descriptors, each with its
own type and name.
Add a common typedef that can be used in generic code.
Also add a stub typedef for architecture without function descriptors,
to avoid a forest of #ifdefs.
It replaces the similar 'func_desc_t' previously defined in
arch/powerpc/kernel/module_64.c
Reviewed-by: Kees Cook <redacted>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Christophe Leroy <redacted>
---
I think that deserves a comment. If it's just to allow ifdef to be
avoided, I guess that's okay with a comment. Would be nice if you could
cause it to generate a link time error if it was ever used like
undefined functions, but I guess you can't. It's not a necessity though.
Thanks,
Nick
Excerpts from Christophe Leroy's message of October 17, 2021 10:38 pm:
quoted
We have three architectures using function descriptors, each with its
own type and name.
Add a common typedef that can be used in generic code.
Also add a stub typedef for architecture without function descriptors,
to avoid a forest of #ifdefs.
It replaces the similar 'func_desc_t' previously defined in
arch/powerpc/kernel/module_64.c
Reviewed-by: Kees Cook <redacted>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Christophe Leroy <redacted>
---
I think that deserves a comment. If it's just to allow ifdef to be
avoided, I guess that's okay with a comment. Would be nice if you could
cause it to generate a link time error if it was ever used like
undefined functions, but I guess you can't. It's not a necessity though.
I tried to explain it in the commit message, but I can add a comment
here in addition for sure.
By the way, it IS used in powerpc's module_64.c:
static func_desc_t func_desc(unsigned long addr)
{
return (func_desc_t){addr};
}
static unsigned long func_addr(unsigned long addr)
{
return func_desc(addr).addr;
}
Excerpts from Christophe Leroy's message of October 17, 2021 10:38 pm:
quoted
In preparation of making func_desc_t generic, change the ELFv2
version to a struct containing 'addr' element.
This allows using single helpers common to ELFv1 and ELFv2.
Signed-off-by: Christophe Leroy <redacted>
@@ -33,19 +33,13 @@#ifdef PPC64_ELF_ABI_v2/* An address is simply the address of the function. */-typedefunsignedlongfunc_desc_t;+typedefstruct{+unsignedlongaddr;+}func_desc_t;
I'm not quite following why this change is done. I guess it is so you
can move this func_desc_t type into core code, but why do that? Is it
just to avoid using the preprocessor?
I explained it in patch 7 but yes it probably also deserves some more
explanation here as well.
That's right, it's to avoid having to spread #ifdefs everywhere.
On its own this patch looks okay.
Acked-by: Nicholas Piggin <npiggin@gmail.com>
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-10-18 09:16:53
Excerpts from Christophe Leroy's message of October 18, 2021 5:07 pm:
Le 18/10/2021 à 08:29, Nicholas Piggin a écrit :
quoted
Excerpts from Christophe Leroy's message of October 17, 2021 10:38 pm:
quoted
We have three architectures using function descriptors, each with its
own type and name.
Add a common typedef that can be used in generic code.
Also add a stub typedef for architecture without function descriptors,
to avoid a forest of #ifdefs.
It replaces the similar 'func_desc_t' previously defined in
arch/powerpc/kernel/module_64.c
Reviewed-by: Kees Cook <redacted>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Christophe Leroy <redacted>
---
I think that deserves a comment. If it's just to allow ifdef to be
avoided, I guess that's okay with a comment. Would be nice if you could
cause it to generate a link time error if it was ever used like
undefined functions, but I guess you can't. It's not a necessity though.
I tried to explain it in the commit message, but I can add a comment
here in addition for sure.
Thanks.
By the way, it IS used in powerpc's module_64.c:
Ah yes of course. I guess the point is function descriptors don't exist
so it should not be used (in general). powerpc module code knows what it
is doing, I guess it's okay for it to use it.
Thanks,
Nick
Hi Kees,
Le 17/10/2021 à 14:38, Christophe Leroy a écrit :
execute_location() and execute_user_location() intent
to copy do_nothing() text and execute it at a new location.
However, at the time being it doesn't copy do_nothing() function
but do_nothing() function descriptor which still points to the
original text. So at the end it still executes do_nothing() at
its original location allthough using a copied function descriptor.
So, fix that by really copying do_nothing() text and build a new
function descriptor by copying do_nothing() function descriptor and
updating the target address with the new location.
Also fix the displayed addresses by dereferencing do_nothing()
function descriptor.
Signed-off-by: Christophe Leroy <redacted>
Do you have any comment to this patch and to patch 12 ?
If not, is it ok to get your acked-by ?
Thanks
Christophe
@@ -44,19 +44,34 @@ static noinline void do_overwritten(void)return;}+staticvoid*setup_function_descriptor(func_desc_t*fdesc,void*dst)+{+if(!have_function_descriptors())+returndst;++memcpy(fdesc,do_nothing,sizeof(*fdesc));+fdesc->addr=(unsignedlong)dst;+barrier();++returnfdesc;+}+staticnoinlinevoidexecute_location(void*dst,boolwrite){-void(*func)(void)=dst;+void(*func)(void);+func_desc_tfdesc;+void*do_nothing_text=dereference_function_descriptor(do_nothing);-pr_info("attempting ok execution at %px\n",do_nothing);+pr_info("attempting ok execution at %px\n",do_nothing_text);do_nothing();if(write==CODE_WRITE){-memcpy(dst,do_nothing,EXEC_SIZE);+memcpy(dst,do_nothing_text,EXEC_SIZE);flush_icache_range((unsignedlong)dst,(unsignedlong)dst+EXEC_SIZE);}-pr_info("attempting bad execution at %px\n",func);+pr_info("attempting bad execution at %px\n",dst);+func=setup_function_descriptor(&fdesc,dst);func();pr_err("FAIL: func returned\n");}
@@ -66,16 +81,19 @@ static void execute_user_location(void *dst)intcopied;/* Intentionally crossing kernel/user memory boundary. */-void(*func)(void)=dst;+void(*func)(void);+func_desc_tfdesc;+void*do_nothing_text=dereference_function_descriptor(do_nothing);-pr_info("attempting ok execution at %px\n",do_nothing);+pr_info("attempting ok execution at %px\n",do_nothing_text);do_nothing();-copied=access_process_vm(current,(unsignedlong)dst,do_nothing,+copied=access_process_vm(current,(unsignedlong)dst,do_nothing_text,EXEC_SIZE,FOLL_WRITE);if(copied<EXEC_SIZE)return;-pr_info("attempting bad execution at %px\n",func);+pr_info("attempting bad execution at %px\n",dst);+func=setup_function_descriptor(&fdesc,dst);func();pr_err("FAIL: func returned\n");}
Hi Kees,
Le 17/10/2021 à 14:38, Christophe Leroy a écrit :
quoted
execute_location() and execute_user_location() intent
to copy do_nothing() text and execute it at a new location.
However, at the time being it doesn't copy do_nothing() function
but do_nothing() function descriptor which still points to the
original text. So at the end it still executes do_nothing() at
its original location allthough using a copied function descriptor.
So, fix that by really copying do_nothing() text and build a new
function descriptor by copying do_nothing() function descriptor and
updating the target address with the new location.
Also fix the displayed addresses by dereferencing do_nothing()
function descriptor.
Signed-off-by: Christophe Leroy <redacted>
Do you have any comment to this patch and to patch 12 ?
If not, is it ok to get your acked-by ?
Hi Christophe,
I think this whole series is a nice cleanup and harmonization
of how function descriptors are used.
At least for the PA-RISC parts you may add:
Acked-by: Helge Deller <deller@gmx.de>
Thanks!
Helge
@@ -44,19 +44,34 @@ static noinline void do_overwritten(void)return;}+staticvoid*setup_function_descriptor(func_desc_t*fdesc,void*dst)+{+if(!have_function_descriptors())+returndst;++memcpy(fdesc,do_nothing,sizeof(*fdesc));+fdesc->addr=(unsignedlong)dst;+barrier();++returnfdesc;+}+staticnoinlinevoidexecute_location(void*dst,boolwrite){-void(*func)(void)=dst;+void(*func)(void);+func_desc_tfdesc;+void*do_nothing_text=dereference_function_descriptor(do_nothing);-pr_info("attempting ok execution at %px\n",do_nothing);+pr_info("attempting ok execution at %px\n",do_nothing_text);do_nothing();if(write==CODE_WRITE){-memcpy(dst,do_nothing,EXEC_SIZE);+memcpy(dst,do_nothing_text,EXEC_SIZE);flush_icache_range((unsignedlong)dst,(unsignedlong)dst+EXEC_SIZE);}-pr_info("attempting bad execution at %px\n",func);+pr_info("attempting bad execution at %px\n",dst);+func=setup_function_descriptor(&fdesc,dst);func();pr_err("FAIL: func returned\n");}
@@ -66,16 +81,19 @@ static void execute_user_location(void *dst)intcopied;/* Intentionally crossing kernel/user memory boundary. */-void(*func)(void)=dst;+void(*func)(void);+func_desc_tfdesc;+void*do_nothing_text=dereference_function_descriptor(do_nothing);-pr_info("attempting ok execution at %px\n",do_nothing);+pr_info("attempting ok execution at %px\n",do_nothing_text);do_nothing();-copied=access_process_vm(current,(unsignedlong)dst,do_nothing,+copied=access_process_vm(current,(unsignedlong)dst,do_nothing_text,EXEC_SIZE,FOLL_WRITE);if(copied<EXEC_SIZE)return;-pr_info("attempting bad execution at %px\n",func);+pr_info("attempting bad execution at %px\n",dst);+func=setup_function_descriptor(&fdesc,dst);func();pr_err("FAIL: func returned\n");}
Hi Kees,
Le 17/12/2021 à 12:49, Christophe Leroy a écrit :
Hi Kees,
Le 17/10/2021 à 14:38, Christophe Leroy a écrit :
quoted
execute_location() and execute_user_location() intent
to copy do_nothing() text and execute it at a new location.
However, at the time being it doesn't copy do_nothing() function
but do_nothing() function descriptor which still points to the
original text. So at the end it still executes do_nothing() at
its original location allthough using a copied function descriptor.
So, fix that by really copying do_nothing() text and build a new
function descriptor by copying do_nothing() function descriptor and
updating the target address with the new location.
Also fix the displayed addresses by dereferencing do_nothing()
function descriptor.
Signed-off-by: Christophe Leroy <redacted>
Do you have any comment to this patch and to patch 12 ?
If not, is it ok to get your acked-by ?
Any feedback please, even if it's to say no feedback ?
Many thanks,
Christophe
On Wed, Jan 19, 2022 at 08:28:54PM +0100, Christophe Leroy wrote:
Hi Kees,
Le 17/12/2021 à 12:49, Christophe Leroy a écrit :
quoted
Hi Kees,
Le 17/10/2021 à 14:38, Christophe Leroy a écrit :
quoted
execute_location() and execute_user_location() intent
to copy do_nothing() text and execute it at a new location.
However, at the time being it doesn't copy do_nothing() function
but do_nothing() function descriptor which still points to the
original text. So at the end it still executes do_nothing() at
its original location allthough using a copied function descriptor.
So, fix that by really copying do_nothing() text and build a new
function descriptor by copying do_nothing() function descriptor and
updating the target address with the new location.
Also fix the displayed addresses by dereferencing do_nothing()
function descriptor.
Signed-off-by: Christophe Leroy <redacted>
Do you have any comment to this patch and to patch 12 ?
If not, is it ok to get your acked-by ?
Any feedback please, even if it's to say no feedback ?
Hi! Thanks for the ping; I haven't had time yet to look at this, but
with -rc1 coming, I should be able to task-switch back to LKDTM for the
dev cycle and I can give some feedback.
-Kees
@@ -159,12 +160,32 @@ int kernel_text_address(unsigned long addr)}/*-*Onsomearchitectures(PPC64,IA64)functionpointers+*Onsomearchitectures(PPC64,IA64,PARISC)functionpointers*areactuallyonlytokenstosomedatathatthenholdsthe*realfunctionaddress.Asaresult,tofindifafunction*pointerispartofthekerneltext,weneedtodosome*specialdereferencingfirst.*/+#ifdef CONFIG_HAVE_FUNCTION_DESCRIPTORS+void*dereference_function_descriptor(void*ptr)+{+func_desc_t*desc=ptr;+void*p;++if(!get_kernel_nofault(p,(void*)&desc->addr))+ptr=p;+returnptr;+}
This needs an EXPORT_SYMBOL_GPL(), otherwise the build breaks after
patch 10 with CONFIG_LKDTM=m.
cheers
On Sun, Oct 17, 2021 at 02:38:14PM +0200, Christophe Leroy wrote:
There are three architectures with function descriptors, try to
have common names for the address they contain in order to
refactor some functions into generic functions later.
powerpc has 'entry'
ia64 has 'ip'
parisc has 'addr'
Vote for 'addr' and update 'func_descr_t' accordingly.
Move it in asm/elf.h to have it at the same place on all
three architectures, remove the typedef which hides its real
type, and change it to a smoother name 'struct func_desc'.
Signed-off-by: Christophe Leroy <redacted>
I like the name. :)
Reviewed-by: Kees Cook <redacted>
--
Kees Cook
On Sun, Oct 17, 2021 at 02:38:17PM +0200, Christophe Leroy wrote:
quoted hunk
In preparation of making func_desc_t generic, change the ELFv2
version to a struct containing 'addr' element.
This allows using single helpers common to ELFv1 and ELFv2.
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/kernel/module_64.c | 32 ++++++++++++++------------------
1 file changed, 14 insertions(+), 18 deletions(-)
@@ -33,19 +33,13 @@#ifdef PPC64_ELF_ABI_v2/* An address is simply the address of the function. */-typedefunsignedlongfunc_desc_t;+typedefstruct{+unsignedlongaddr;+}func_desc_t;staticfunc_desc_tfunc_desc(unsignedlongaddr){-returnaddr;-}-staticunsignedlongfunc_addr(unsignedlongaddr)-{-returnaddr;-}-staticunsignedlongstub_func_addr(func_desc_tfunc)-{-returnfunc;+return(func_desc_t){addr};
There's only 1 element in the struct, so okay, but it hurt my eyes a
little. I would have been happier with:
return (func_desc_t){ .addr = addr; };
But of course that also looks bonkers because it starts with "return".
So no matter what I do my eyes bug out. ;)
So it's fine either way. :)
Reviewed-by: Kees Cook <redacted>
quoted hunk
}
/* PowerPC64 specific values for the Elf64_Sym st_other field. */
@@ -70,14 +64,6 @@ static func_desc_t func_desc(unsigned long addr) { return *(struct func_desc *)addr; }-static unsigned long func_addr(unsigned long addr)-{- return func_desc(addr).addr;-}-static unsigned long stub_func_addr(func_desc_t func)-{- return func.addr;-} static unsigned int local_entry_offset(const Elf64_Sym *sym) { return 0;
@@ -93,6 +79,16 @@ void *dereference_module_function_descriptor(struct module *mod, void *ptr) } #endif+static unsigned long func_addr(unsigned long addr)+{+ return func_desc(addr).addr;+}++static unsigned long stub_func_addr(func_desc_t func)+{+ return func.addr;+}+ #define STUB_MAGIC 0x73747562 /* stub */ /* Like PPC32, we need little trampolines to do > 24-bit jumps (into
@@ -159,12 +160,32 @@ int kernel_text_address(unsigned long addr)}/*-*Onsomearchitectures(PPC64,IA64)functionpointers+*Onsomearchitectures(PPC64,IA64,PARISC)functionpointers*areactuallyonlytokenstosomedatathatthenholdsthe*realfunctionaddress.Asaresult,tofindifafunction*pointerispartofthekerneltext,weneedtodosome*specialdereferencingfirst.*/+#ifdef CONFIG_HAVE_FUNCTION_DESCRIPTORS+void*dereference_function_descriptor(void*ptr)+{+func_desc_t*desc=ptr;+void*p;++if(!get_kernel_nofault(p,(void*)&desc->addr))+ptr=p;+returnptr;+}
This needs an EXPORT_SYMBOL_GPL(), otherwise the build breaks after
patch 10 with CONFIG_LKDTM=m.
Oh good catch!
(There have been a few cases of LKDTM=m being the only thing needed a
symbol, so I've pondered giving it a namespace or constructing a little
ifdef wrapper... but this seems ok to export...)
--
Kees Cook
On Sun, Oct 17, 2021 at 02:38:24PM +0200, Christophe Leroy wrote:
execute_location() and execute_user_location() intent
to copy do_nothing() text and execute it at a new location.
However, at the time being it doesn't copy do_nothing() function
but do_nothing() function descriptor which still points to the
original text. So at the end it still executes do_nothing() at
its original location allthough using a copied function descriptor.
So, fix that by really copying do_nothing() text and build a new
function descriptor by copying do_nothing() function descriptor and
updating the target address with the new location.
Also fix the displayed addresses by dereferencing do_nothing()
function descriptor.
Signed-off-by: Christophe Leroy <redacted>
This looks good. I might rename variables in the future (e.g. to avoid
the churn from adding _text) but also, that does help keep it clear. :)
Acked-by: Kees Cook <redacted>
-Kees
@@ -44,19 +44,34 @@ static noinline void do_overwritten(void)return;}+staticvoid*setup_function_descriptor(func_desc_t*fdesc,void*dst)+{+if(!have_function_descriptors())+returndst;++memcpy(fdesc,do_nothing,sizeof(*fdesc));+fdesc->addr=(unsignedlong)dst;+barrier();++returnfdesc;+}+staticnoinlinevoidexecute_location(void*dst,boolwrite){-void(*func)(void)=dst;+void(*func)(void);+func_desc_tfdesc;+void*do_nothing_text=dereference_function_descriptor(do_nothing);-pr_info("attempting ok execution at %px\n",do_nothing);+pr_info("attempting ok execution at %px\n",do_nothing_text);do_nothing();if(write==CODE_WRITE){-memcpy(dst,do_nothing,EXEC_SIZE);+memcpy(dst,do_nothing_text,EXEC_SIZE);flush_icache_range((unsignedlong)dst,(unsignedlong)dst+EXEC_SIZE);}-pr_info("attempting bad execution at %px\n",func);+pr_info("attempting bad execution at %px\n",dst);+func=setup_function_descriptor(&fdesc,dst);func();pr_err("FAIL: func returned\n");}
@@ -66,16 +81,19 @@ static void execute_user_location(void *dst)intcopied;/* Intentionally crossing kernel/user memory boundary. */-void(*func)(void)=dst;+void(*func)(void);+func_desc_tfdesc;+void*do_nothing_text=dereference_function_descriptor(do_nothing);-pr_info("attempting ok execution at %px\n",do_nothing);+pr_info("attempting ok execution at %px\n",do_nothing_text);do_nothing();-copied=access_process_vm(current,(unsignedlong)dst,do_nothing,+copied=access_process_vm(current,(unsignedlong)dst,do_nothing_text,EXEC_SIZE,FOLL_WRITE);if(copied<EXEC_SIZE)return;-pr_info("attempting bad execution at %px\n",func);+pr_info("attempting bad execution at %px\n",dst);+func=setup_function_descriptor(&fdesc,dst);func();pr_err("FAIL: func returned\n");}
On Sun, Oct 17, 2021 at 02:38:25PM +0200, Christophe Leroy wrote:
quoted hunk
Add WRITE_OPD to check that you can't modify function
descriptors.
Gives the following result when function descriptors are
not protected:
lkdtm: Performing direct entry WRITE_OPD
lkdtm: attempting bad 16 bytes write at c00000000269b358
lkdtm: FAIL: survived bad write
lkdtm: do_nothing was hijacked!
Looks like a standard compiler barrier() is not enough to force
GCC to use the modified function descriptor. Had to add a fake empty
inline assembly to force GCC to reload the function descriptor.
Signed-off-by: Christophe Leroy <redacted>
---
drivers/misc/lkdtm/core.c | 1 +
drivers/misc/lkdtm/lkdtm.h | 1 +
drivers/misc/lkdtm/perms.c | 22 ++++++++++++++++++++++
3 files changed, 24 insertions(+)
@@ -44,6 +44,11 @@ static noinline void do_overwritten(void)return;}+staticnoinlinevoiddo_almost_nothing(void)+{+pr_info("do_nothing was hijacked!\n");+}+staticvoid*setup_function_descriptor(func_desc_t*fdesc,void*dst){if(!have_function_descriptors())
@@ -144,6 +149,23 @@ void lkdtm_WRITE_KERN(void)do_overwritten();}+voidlkdtm_WRITE_OPD(void)+{+size_tsize=sizeof(func_desc_t);+void(*func)(void)=do_nothing;++if(!have_function_descriptors()){+pr_info("XFAIL: Platform doesn't use function descriptors.\n");+return;+}+pr_info("attempting bad %zu bytes write at %px\n",size,do_nothing);+memcpy(do_nothing,do_almost_nothing,size);+pr_err("FAIL: survived bad write\n");
Non-function-descriptor architectures would successfully crash at the
memcpy too, right? (i.e. for them this is just repeating WRITE_KERN)
I'm pondering the utility of the XFAIL vs just letting is succeed, but I
think it more accurate to say "hey, no OPD" as you have it.
(Though for the future I've been considering making the selftests an
opt-out list so the "normal" stuff doesn't need to keep getting added
there.)
Thanks!
Acked-by: Kees Cook <redacted>
-Kees
--
Kees Cook
On Thu, Feb 10, 2022 at 04:54:52PM -0800, Kees Cook wrote:
On Sun, Oct 17, 2021 at 02:38:17PM +0200, Christophe Leroy wrote:
(edited:)
quoted
+typedef struct {
+ unsigned long addr;
+} func_desc_t;
static func_desc_t func_desc(unsigned long addr)
{
+ return (func_desc_t){addr};
There's only 1 element in the struct, so okay, but it hurt my eyes a
little. I would have been happier with:
return (func_desc_t){ .addr = addr; };
But of course that also looks bonkers because it starts with "return".
So no matter what I do my eyes bug out. ;)
The usual way to avoid convoluted constructs is to name more factors.
So:
static func_desc_t func_desc(unsigned long addr)
{
func_desc_t desc = {};
desc.addr = addr;
return desc;
}
Segher
On Sun, Oct 17, 2021 at 02:38:17PM +0200, Christophe Leroy wrote:
quoted
In preparation of making func_desc_t generic, change the ELFv2
version to a struct containing 'addr' element.
This allows using single helpers common to ELFv1 and ELFv2.
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/kernel/module_64.c | 32 ++++++++++++++------------------
1 file changed, 14 insertions(+), 18 deletions(-)
@@ -33,19 +33,13 @@#ifdef PPC64_ELF_ABI_v2/* An address is simply the address of the function. */-typedefunsignedlongfunc_desc_t;+typedefstruct{+unsignedlongaddr;+}func_desc_t;staticfunc_desc_tfunc_desc(unsignedlongaddr){-returnaddr;-}-staticunsignedlongfunc_addr(unsignedlongaddr)-{-returnaddr;-}-staticunsignedlongstub_func_addr(func_desc_tfunc)-{-returnfunc;+return(func_desc_t){addr};
There's only 1 element in the struct, so okay, but it hurt my eyes a
little. I would have been happier with:
return (func_desc_t){ .addr = addr; };
But of course that also looks bonkers because it starts with "return".
So no matter what I do my eyes bug out. ;)
So it's fine either way. :)
Reviewed-by: Kees Cook <redacted>
I am going for:
static func_desc_t func_desc(unsigned long addr)
{
+ func_desc_t desc = {
+ .addr = addr,
+ };
+
+ return desc;
}
Thanks
Christophe
@@ -159,12 +160,32 @@ int kernel_text_address(unsigned long addr)}/*-*Onsomearchitectures(PPC64,IA64)functionpointers+*Onsomearchitectures(PPC64,IA64,PARISC)functionpointers*areactuallyonlytokenstosomedatathatthenholdsthe*realfunctionaddress.Asaresult,tofindifafunction*pointerispartofthekerneltext,weneedtodosome*specialdereferencingfirst.*/+#ifdef CONFIG_HAVE_FUNCTION_DESCRIPTORS+void*dereference_function_descriptor(void*ptr)+{+func_desc_t*desc=ptr;+void*p;++if(!get_kernel_nofault(p,(void*)&desc->addr))+ptr=p;+returnptr;+}
This needs an EXPORT_SYMBOL_GPL(), otherwise the build breaks after
patch 10 with CONFIG_LKDTM=m.
Oh good catch!
(There have been a few cases of LKDTM=m being the only thing needed a
symbol, so I've pondered giving it a namespace or constructing a little
ifdef wrapper... but this seems ok to export...)
powerpc and ia64 had it as a static inline, but parisc had it as a plain
function and didn't export it. So I guess the export is not required at
this point. I will export it in patch 10 when it becomes necessary.
Christophe
On Sun, Oct 17, 2021 at 02:38:25PM +0200, Christophe Leroy wrote:
quoted
Add WRITE_OPD to check that you can't modify function
descriptors.
Gives the following result when function descriptors are
not protected:
lkdtm: Performing direct entry WRITE_OPD
lkdtm: attempting bad 16 bytes write at c00000000269b358
lkdtm: FAIL: survived bad write
lkdtm: do_nothing was hijacked!
Looks like a standard compiler barrier() is not enough to force
GCC to use the modified function descriptor. Had to add a fake empty
inline assembly to force GCC to reload the function descriptor.
Signed-off-by: Christophe Leroy <redacted>
---
drivers/misc/lkdtm/core.c | 1 +
drivers/misc/lkdtm/lkdtm.h | 1 +
drivers/misc/lkdtm/perms.c | 22 ++++++++++++++++++++++
3 files changed, 24 insertions(+)
@@ -44,6 +44,11 @@ static noinline void do_overwritten(void)return;}+staticnoinlinevoiddo_almost_nothing(void)+{+pr_info("do_nothing was hijacked!\n");+}+staticvoid*setup_function_descriptor(func_desc_t*fdesc,void*dst){if(!have_function_descriptors())
@@ -144,6 +149,23 @@ void lkdtm_WRITE_KERN(void)do_overwritten();}+voidlkdtm_WRITE_OPD(void)+{+size_tsize=sizeof(func_desc_t);+void(*func)(void)=do_nothing;++if(!have_function_descriptors()){+pr_info("XFAIL: Platform doesn't use function descriptors.\n");+return;+}+pr_info("attempting bad %zu bytes write at %px\n",size,do_nothing);+memcpy(do_nothing,do_almost_nothing,size);+pr_err("FAIL: survived bad write\n");
Non-function-descriptor architectures would successfully crash at the
memcpy too, right? (i.e. for them this is just repeating WRITE_KERN)
Yes it should. But not for the good reason.
quoted hunk
I'm pondering the utility of the XFAIL vs just letting is succeed, but I
think it more accurate to say "hey, no OPD" as you have it.
(Though for the future I've been considering making the selftests an
opt-out list so the "normal" stuff doesn't need to keep getting added
there.)
Thanks!
Acked-by: Kees Cook <redacted>