mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-01 00:58:39 +02:00 
			
		
		
		
	static_call: Allow module use without exposing static_call_key
When exporting static_call_key; with EXPORT_STATIC_CALL*(), the module can use static_call_update() to change the function called. This is not desirable in general. Not exporting static_call_key however also disallows usage of static_call(), since objtool needs the key to construct the static_call_site. Solve this by allowing objtool to create the static_call_site using the trampoline address when it builds a module and cannot find the static_call_key symbol. The module loader will then try and map the trampole back to a key before it constructs the normal sites list. Doing this requires a trampoline -> key associsation, so add another magic section that keeps those. Originally-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Ingo Molnar <mingo@kernel.org> Link: https://lkml.kernel.org/r/20210127231837.ifddpn7rhwdaepiu@treble
This commit is contained in:
		
							parent
							
								
									e59e10f8ef
								
							
						
					
					
						commit
						73f44fe19d
					
				
					 7 changed files with 149 additions and 11 deletions
				
			
		|  | @ -37,4 +37,11 @@ | ||||||
| #define ARCH_DEFINE_STATIC_CALL_NULL_TRAMP(name)			\ | #define ARCH_DEFINE_STATIC_CALL_NULL_TRAMP(name)			\ | ||||||
| 	__ARCH_DEFINE_STATIC_CALL_TRAMP(name, "ret; nop; nop; nop; nop") | 	__ARCH_DEFINE_STATIC_CALL_TRAMP(name, "ret; nop; nop; nop; nop") | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | #define ARCH_ADD_TRAMP_KEY(name)					\ | ||||||
|  | 	asm(".pushsection .static_call_tramp_key, \"a\"		\n"	\ | ||||||
|  | 	    ".long " STATIC_CALL_TRAMP_STR(name) " - .		\n"	\ | ||||||
|  | 	    ".long " STATIC_CALL_KEY_STR(name) " - .		\n"	\ | ||||||
|  | 	    ".popsection					\n") | ||||||
|  | 
 | ||||||
| #endif /* _ASM_STATIC_CALL_H */ | #endif /* _ASM_STATIC_CALL_H */ | ||||||
|  |  | ||||||
|  | @ -393,7 +393,10 @@ | ||||||
| 	. = ALIGN(8);							\ | 	. = ALIGN(8);							\ | ||||||
| 	__start_static_call_sites = .;					\ | 	__start_static_call_sites = .;					\ | ||||||
| 	KEEP(*(.static_call_sites))					\ | 	KEEP(*(.static_call_sites))					\ | ||||||
| 	__stop_static_call_sites = .; | 	__stop_static_call_sites = .;					\ | ||||||
|  | 	__start_static_call_tramp_key = .;				\ | ||||||
|  | 	KEEP(*(.static_call_tramp_key))					\ | ||||||
|  | 	__stop_static_call_tramp_key = .; | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Allow architectures to handle ro_after_init data on their |  * Allow architectures to handle ro_after_init data on their | ||||||
|  |  | ||||||
|  | @ -138,6 +138,12 @@ struct static_call_key { | ||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /* For finding the key associated with a trampoline */ | ||||||
|  | struct static_call_tramp_key { | ||||||
|  | 	s32 tramp; | ||||||
|  | 	s32 key; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| extern void __static_call_update(struct static_call_key *key, void *tramp, void *func); | extern void __static_call_update(struct static_call_key *key, void *tramp, void *func); | ||||||
| extern int static_call_mod_init(struct module *mod); | extern int static_call_mod_init(struct module *mod); | ||||||
| extern int static_call_text_reserved(void *start, void *end); | extern int static_call_text_reserved(void *start, void *end); | ||||||
|  | @ -165,11 +171,18 @@ extern long __static_call_return0(void); | ||||||
| #define EXPORT_STATIC_CALL(name)					\ | #define EXPORT_STATIC_CALL(name)					\ | ||||||
| 	EXPORT_SYMBOL(STATIC_CALL_KEY(name));				\ | 	EXPORT_SYMBOL(STATIC_CALL_KEY(name));				\ | ||||||
| 	EXPORT_SYMBOL(STATIC_CALL_TRAMP(name)) | 	EXPORT_SYMBOL(STATIC_CALL_TRAMP(name)) | ||||||
| 
 |  | ||||||
| #define EXPORT_STATIC_CALL_GPL(name)					\ | #define EXPORT_STATIC_CALL_GPL(name)					\ | ||||||
| 	EXPORT_SYMBOL_GPL(STATIC_CALL_KEY(name));			\ | 	EXPORT_SYMBOL_GPL(STATIC_CALL_KEY(name));			\ | ||||||
| 	EXPORT_SYMBOL_GPL(STATIC_CALL_TRAMP(name)) | 	EXPORT_SYMBOL_GPL(STATIC_CALL_TRAMP(name)) | ||||||
| 
 | 
 | ||||||
|  | /* Leave the key unexported, so modules can't change static call targets: */ | ||||||
|  | #define EXPORT_STATIC_CALL_TRAMP(name)					\ | ||||||
|  | 	EXPORT_SYMBOL(STATIC_CALL_TRAMP(name));				\ | ||||||
|  | 	ARCH_ADD_TRAMP_KEY(name) | ||||||
|  | #define EXPORT_STATIC_CALL_TRAMP_GPL(name)				\ | ||||||
|  | 	EXPORT_SYMBOL_GPL(STATIC_CALL_TRAMP(name));			\ | ||||||
|  | 	ARCH_ADD_TRAMP_KEY(name) | ||||||
|  | 
 | ||||||
| #elif defined(CONFIG_HAVE_STATIC_CALL) | #elif defined(CONFIG_HAVE_STATIC_CALL) | ||||||
| 
 | 
 | ||||||
| static inline int static_call_init(void) { return 0; } | static inline int static_call_init(void) { return 0; } | ||||||
|  | @ -216,11 +229,16 @@ static inline long __static_call_return0(void) | ||||||
| #define EXPORT_STATIC_CALL(name)					\ | #define EXPORT_STATIC_CALL(name)					\ | ||||||
| 	EXPORT_SYMBOL(STATIC_CALL_KEY(name));				\ | 	EXPORT_SYMBOL(STATIC_CALL_KEY(name));				\ | ||||||
| 	EXPORT_SYMBOL(STATIC_CALL_TRAMP(name)) | 	EXPORT_SYMBOL(STATIC_CALL_TRAMP(name)) | ||||||
| 
 |  | ||||||
| #define EXPORT_STATIC_CALL_GPL(name)					\ | #define EXPORT_STATIC_CALL_GPL(name)					\ | ||||||
| 	EXPORT_SYMBOL_GPL(STATIC_CALL_KEY(name));			\ | 	EXPORT_SYMBOL_GPL(STATIC_CALL_KEY(name));			\ | ||||||
| 	EXPORT_SYMBOL_GPL(STATIC_CALL_TRAMP(name)) | 	EXPORT_SYMBOL_GPL(STATIC_CALL_TRAMP(name)) | ||||||
| 
 | 
 | ||||||
|  | /* Leave the key unexported, so modules can't change static call targets: */ | ||||||
|  | #define EXPORT_STATIC_CALL_TRAMP(name)					\ | ||||||
|  | 	EXPORT_SYMBOL(STATIC_CALL_TRAMP(name)) | ||||||
|  | #define EXPORT_STATIC_CALL_TRAMP_GPL(name)				\ | ||||||
|  | 	EXPORT_SYMBOL_GPL(STATIC_CALL_TRAMP(name)) | ||||||
|  | 
 | ||||||
| #else /* Generic implementation */ | #else /* Generic implementation */ | ||||||
| 
 | 
 | ||||||
| static inline int static_call_init(void) { return 0; } | static inline int static_call_init(void) { return 0; } | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
| #define STATIC_CALL_KEY_PREFIX_STR	__stringify(STATIC_CALL_KEY_PREFIX) | #define STATIC_CALL_KEY_PREFIX_STR	__stringify(STATIC_CALL_KEY_PREFIX) | ||||||
| #define STATIC_CALL_KEY_PREFIX_LEN	(sizeof(STATIC_CALL_KEY_PREFIX_STR) - 1) | #define STATIC_CALL_KEY_PREFIX_LEN	(sizeof(STATIC_CALL_KEY_PREFIX_STR) - 1) | ||||||
| #define STATIC_CALL_KEY(name)		__PASTE(STATIC_CALL_KEY_PREFIX, name) | #define STATIC_CALL_KEY(name)		__PASTE(STATIC_CALL_KEY_PREFIX, name) | ||||||
|  | #define STATIC_CALL_KEY_STR(name)	__stringify(STATIC_CALL_KEY(name)) | ||||||
| 
 | 
 | ||||||
| #define STATIC_CALL_TRAMP_PREFIX	__SCT__ | #define STATIC_CALL_TRAMP_PREFIX	__SCT__ | ||||||
| #define STATIC_CALL_TRAMP_PREFIX_STR	__stringify(STATIC_CALL_TRAMP_PREFIX) | #define STATIC_CALL_TRAMP_PREFIX_STR	__stringify(STATIC_CALL_TRAMP_PREFIX) | ||||||
|  | @ -39,17 +40,39 @@ struct static_call_site { | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_HAVE_STATIC_CALL | #ifdef CONFIG_HAVE_STATIC_CALL | ||||||
| 
 | 
 | ||||||
|  | #define __raw_static_call(name)	(&STATIC_CALL_TRAMP(name)) | ||||||
|  | 
 | ||||||
|  | #ifdef CONFIG_HAVE_STATIC_CALL_INLINE | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * __ADDRESSABLE() is used to ensure the key symbol doesn't get stripped from |  * __ADDRESSABLE() is used to ensure the key symbol doesn't get stripped from | ||||||
|  * the symbol table so that objtool can reference it when it generates the |  * the symbol table so that objtool can reference it when it generates the | ||||||
|  * .static_call_sites section. |  * .static_call_sites section. | ||||||
|  */ |  */ | ||||||
|  | #define __STATIC_CALL_ADDRESSABLE(name) \ | ||||||
|  | 	__ADDRESSABLE(STATIC_CALL_KEY(name)) | ||||||
|  | 
 | ||||||
| #define __static_call(name)						\ | #define __static_call(name)						\ | ||||||
| ({									\ | ({									\ | ||||||
| 	__ADDRESSABLE(STATIC_CALL_KEY(name));				\ | 	__STATIC_CALL_ADDRESSABLE(name);				\ | ||||||
| 	&STATIC_CALL_TRAMP(name);					\ | 	__raw_static_call(name);					\ | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | #else /* !CONFIG_HAVE_STATIC_CALL_INLINE */ | ||||||
|  | 
 | ||||||
|  | #define __STATIC_CALL_ADDRESSABLE(name) | ||||||
|  | #define __static_call(name)	__raw_static_call(name) | ||||||
|  | 
 | ||||||
|  | #endif /* CONFIG_HAVE_STATIC_CALL_INLINE */ | ||||||
|  | 
 | ||||||
|  | #ifdef MODULE | ||||||
|  | #define __STATIC_CALL_MOD_ADDRESSABLE(name) | ||||||
|  | #define static_call_mod(name)	__raw_static_call(name) | ||||||
|  | #else | ||||||
|  | #define __STATIC_CALL_MOD_ADDRESSABLE(name) __STATIC_CALL_ADDRESSABLE(name) | ||||||
|  | #define static_call_mod(name)	__static_call(name) | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #define static_call(name)	__static_call(name) | #define static_call(name)	__static_call(name) | ||||||
| 
 | 
 | ||||||
| #else | #else | ||||||
|  |  | ||||||
|  | @ -12,6 +12,8 @@ | ||||||
| 
 | 
 | ||||||
| extern struct static_call_site __start_static_call_sites[], | extern struct static_call_site __start_static_call_sites[], | ||||||
| 			       __stop_static_call_sites[]; | 			       __stop_static_call_sites[]; | ||||||
|  | extern struct static_call_tramp_key __start_static_call_tramp_key[], | ||||||
|  | 				    __stop_static_call_tramp_key[]; | ||||||
| 
 | 
 | ||||||
| static bool static_call_initialized; | static bool static_call_initialized; | ||||||
| 
 | 
 | ||||||
|  | @ -323,10 +325,59 @@ static int __static_call_mod_text_reserved(void *start, void *end) | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static unsigned long tramp_key_lookup(unsigned long addr) | ||||||
|  | { | ||||||
|  | 	struct static_call_tramp_key *start = __start_static_call_tramp_key; | ||||||
|  | 	struct static_call_tramp_key *stop = __stop_static_call_tramp_key; | ||||||
|  | 	struct static_call_tramp_key *tramp_key; | ||||||
|  | 
 | ||||||
|  | 	for (tramp_key = start; tramp_key != stop; tramp_key++) { | ||||||
|  | 		unsigned long tramp; | ||||||
|  | 
 | ||||||
|  | 		tramp = (long)tramp_key->tramp + (long)&tramp_key->tramp; | ||||||
|  | 		if (tramp == addr) | ||||||
|  | 			return (long)tramp_key->key + (long)&tramp_key->key; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int static_call_add_module(struct module *mod) | static int static_call_add_module(struct module *mod) | ||||||
| { | { | ||||||
| 	return __static_call_init(mod, mod->static_call_sites, | 	struct static_call_site *start = mod->static_call_sites; | ||||||
| 				  mod->static_call_sites + mod->num_static_call_sites); | 	struct static_call_site *stop = start + mod->num_static_call_sites; | ||||||
|  | 	struct static_call_site *site; | ||||||
|  | 
 | ||||||
|  | 	for (site = start; site != stop; site++) { | ||||||
|  | 		unsigned long addr = (unsigned long)static_call_key(site); | ||||||
|  | 		unsigned long key; | ||||||
|  | 
 | ||||||
|  | 		/*
 | ||||||
|  | 		 * Is the key is exported, 'addr' points to the key, which | ||||||
|  | 		 * means modules are allowed to call static_call_update() on | ||||||
|  | 		 * it. | ||||||
|  | 		 * | ||||||
|  | 		 * Otherwise, the key isn't exported, and 'addr' points to the | ||||||
|  | 		 * trampoline so we need to lookup the key. | ||||||
|  | 		 * | ||||||
|  | 		 * We go through this dance to prevent crazy modules from | ||||||
|  | 		 * abusing sensitive static calls. | ||||||
|  | 		 */ | ||||||
|  | 		if (!kernel_text_address(addr)) | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		key = tramp_key_lookup(addr); | ||||||
|  | 		if (!key) { | ||||||
|  | 			pr_warn("Failed to fixup __raw_static_call() usage at: %ps\n", | ||||||
|  | 				static_call_addr(site)); | ||||||
|  | 			return -EINVAL; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		site->key = (key - (long)&site->key) | | ||||||
|  | 			    (site->key & STATIC_CALL_SITE_FLAGS); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return __static_call_init(mod, start, stop); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void static_call_del_module(struct module *mod) | static void static_call_del_module(struct module *mod) | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
| #define STATIC_CALL_KEY_PREFIX_STR	__stringify(STATIC_CALL_KEY_PREFIX) | #define STATIC_CALL_KEY_PREFIX_STR	__stringify(STATIC_CALL_KEY_PREFIX) | ||||||
| #define STATIC_CALL_KEY_PREFIX_LEN	(sizeof(STATIC_CALL_KEY_PREFIX_STR) - 1) | #define STATIC_CALL_KEY_PREFIX_LEN	(sizeof(STATIC_CALL_KEY_PREFIX_STR) - 1) | ||||||
| #define STATIC_CALL_KEY(name)		__PASTE(STATIC_CALL_KEY_PREFIX, name) | #define STATIC_CALL_KEY(name)		__PASTE(STATIC_CALL_KEY_PREFIX, name) | ||||||
|  | #define STATIC_CALL_KEY_STR(name)	__stringify(STATIC_CALL_KEY(name)) | ||||||
| 
 | 
 | ||||||
| #define STATIC_CALL_TRAMP_PREFIX	__SCT__ | #define STATIC_CALL_TRAMP_PREFIX	__SCT__ | ||||||
| #define STATIC_CALL_TRAMP_PREFIX_STR	__stringify(STATIC_CALL_TRAMP_PREFIX) | #define STATIC_CALL_TRAMP_PREFIX_STR	__stringify(STATIC_CALL_TRAMP_PREFIX) | ||||||
|  | @ -39,17 +40,39 @@ struct static_call_site { | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_HAVE_STATIC_CALL | #ifdef CONFIG_HAVE_STATIC_CALL | ||||||
| 
 | 
 | ||||||
|  | #define __raw_static_call(name)	(&STATIC_CALL_TRAMP(name)) | ||||||
|  | 
 | ||||||
|  | #ifdef CONFIG_HAVE_STATIC_CALL_INLINE | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * __ADDRESSABLE() is used to ensure the key symbol doesn't get stripped from |  * __ADDRESSABLE() is used to ensure the key symbol doesn't get stripped from | ||||||
|  * the symbol table so that objtool can reference it when it generates the |  * the symbol table so that objtool can reference it when it generates the | ||||||
|  * .static_call_sites section. |  * .static_call_sites section. | ||||||
|  */ |  */ | ||||||
|  | #define __STATIC_CALL_ADDRESSABLE(name) \ | ||||||
|  | 	__ADDRESSABLE(STATIC_CALL_KEY(name)) | ||||||
|  | 
 | ||||||
| #define __static_call(name)						\ | #define __static_call(name)						\ | ||||||
| ({									\ | ({									\ | ||||||
| 	__ADDRESSABLE(STATIC_CALL_KEY(name));				\ | 	__STATIC_CALL_ADDRESSABLE(name);				\ | ||||||
| 	&STATIC_CALL_TRAMP(name);					\ | 	__raw_static_call(name);					\ | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | #else /* !CONFIG_HAVE_STATIC_CALL_INLINE */ | ||||||
|  | 
 | ||||||
|  | #define __STATIC_CALL_ADDRESSABLE(name) | ||||||
|  | #define __static_call(name)	__raw_static_call(name) | ||||||
|  | 
 | ||||||
|  | #endif /* CONFIG_HAVE_STATIC_CALL_INLINE */ | ||||||
|  | 
 | ||||||
|  | #ifdef MODULE | ||||||
|  | #define __STATIC_CALL_MOD_ADDRESSABLE(name) | ||||||
|  | #define static_call_mod(name)	__raw_static_call(name) | ||||||
|  | #else | ||||||
|  | #define __STATIC_CALL_MOD_ADDRESSABLE(name) __STATIC_CALL_ADDRESSABLE(name) | ||||||
|  | #define static_call_mod(name)	__static_call(name) | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #define static_call(name)	__static_call(name) | #define static_call(name)	__static_call(name) | ||||||
| 
 | 
 | ||||||
| #else | #else | ||||||
|  |  | ||||||
|  | @ -502,8 +502,21 @@ static int create_static_call_sections(struct objtool_file *file) | ||||||
| 
 | 
 | ||||||
| 		key_sym = find_symbol_by_name(file->elf, tmp); | 		key_sym = find_symbol_by_name(file->elf, tmp); | ||||||
| 		if (!key_sym) { | 		if (!key_sym) { | ||||||
| 			WARN("static_call: can't find static_call_key symbol: %s", tmp); | 			if (!module) { | ||||||
| 			return -1; | 				WARN("static_call: can't find static_call_key symbol: %s", tmp); | ||||||
|  | 				return -1; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			/*
 | ||||||
|  | 			 * For modules(), the key might not be exported, which | ||||||
|  | 			 * means the module can make static calls but isn't | ||||||
|  | 			 * allowed to change them. | ||||||
|  | 			 * | ||||||
|  | 			 * In that case we temporarily set the key to be the | ||||||
|  | 			 * trampoline address.  This is fixed up in | ||||||
|  | 			 * static_call_add_module(). | ||||||
|  | 			 */ | ||||||
|  | 			key_sym = insn->call_dest; | ||||||
| 		} | 		} | ||||||
| 		free(key_name); | 		free(key_name); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Josh Poimboeuf
						Josh Poimboeuf