make call_usermodehelper a bit more "safe"
Subsystems (18)
Greg KH
maintainercover make call_usermodehelper a bit more "safe"
Hi all,
Here's a proof-of-concept patch series that tries to work to address the issue of call_usermodehelper being abused to have the kernel call any userspace binary with full root permissions.
The issue is that if you end up getting write access to kernel memory, if you change the string '/sbin/hotplug' to point to '/home/hacked/my_binary', then the next uevent that the system makes will call this binary instead of the "trusted" one.
It does this by moving the location of the binary to be in read-only memory. This works for a number of call_usermodehelper strings, as they are specified at build or configuration time. But, some subsystems have the option to let userspace change the value at runtime, so those values can't live in read-only memory. To resolve this I've created a new configuration option, CONFIG_READONLY_USERMODEHELPER to make those options not able to be changed.
Yes, this changes existing functionality, but I'm willing to bet that almost no one ever changes these binary locations, or if they do, they can set them to the "correct" location at built time.
This all happens in the last patch of the series. Note, I haven't caught all places in the kernel that has these options, the messiest being coredumps, which I haven't addressed yet, and is going to be a pain.
This last patch is hacky, and I'm not really happy about it, so I'm posting it here as an RFC to see what others think.
As a contrast, grsec does try to mitigate this same problem, but it does so by looking at the location of the binary that is about to be run, and only allowing a small whitelist of directories that are "allowed" to be used. That's a much simpler solution, but also feels hacky to me in a way given that it's a whitelist and encompasses whole system directories (i.e. /sbin/). My patchset requires that each caller of call_usermodehelper be audited, which is a pain, and will be needed to be watched out for for new users, which also isn't any good.
So, anyone have any better ideas? Is this approach worth it? Or should we just go down the "whitelist" path?
Note, the first 3 patches in this series will be submitted for inclusion either way, as they are good cleanups, and change no functionality at all, and resolve this issue automatically for some subsystems with no downside.
thanks,
greg k-h
Greg KH
maintainerPATCH 1/4 kmod: make usermodehelper path a const string
This is in preparation for making it so that usermode helper programs can't be changed, if desired, by userspace. We will tackle the mess of cleaning up the write-ability of argv and env later, that's going to take more work, for much less gain...
--- include/linux/kmod.h | 7 ++++--- kernel/kmod.c | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-)
include/linux/kmod.h +4 -3
@@ -56,7 +56,7 @@ struct file;struct subprocess_info {struct work_struct work;struct completion *complete; -char *path; +const char *path;char **argv;char **envp;int wait;
@@ -67,10 +67,11 @@ struct subprocess_info { };extern int -call_usermodehelper (char *path,char **argv,char **envp,int wait); +call_usermodehelper (const char *path,char **argv,char **envp,int wait);extern struct subprocess_info * -call_usermodehelper_setup (char *path,char **argv,char **envp,gfp_t gfp_mask, +call_usermodehelper_setup (const char *path,char **argv,char **envp, +gfp_t gfp_mask,int (*init)(struct subprocess_info *info,struct cred *new),void (*cleanup)(struct subprocess_info *),void *data);
kernel/kmod.c +2 -2
@@ -516,7 +516,7 @@ static void helper_unlock(void)* Function must be runnable in either a process context or the * context in which call_usermodehelper_exec is called. */ -struct subprocess_info *call_usermodehelper_setup(char *path, char **argv, +struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv, char **envp, gfp_t gfp_mask, int (*init)(struct subprocess_info *info, struct cred *new), void (*cleanup)(struct subprocess_info *info),
@@ -613,7 +613,7 @@ EXPORT_SYMBOL(call_usermodehelper_exec);* This function is the equivalent to use call_usermodehelper_setup() and * call_usermodehelper_exec(). */ -int call_usermodehelper (char *path,char **argv,char **envp,int wait) +int call_usermodehelper (const char *path,char **argv,char **envp,int wait) {struct subprocess_info *info;gfp_t gfp_mask = (wait ==UMH_NO_WAIT ) ?GFP_ATOMIC :GFP_KERNEL ;
Signature · 1 line
2.10.2
Greg KH
maintainerPATCH 2/4 drbd: rename "usermode_helper" to "drbd_usermode_helper"
In reply to Greg KHNothing like having a very generic global variable in a tiny driver subsystem to make a mess of the global namespace...
Anyway, clean it up in anticipation of making drbd_usermode_helper read-only in a future patch.
Note, there are many other "generic" named global variables in the drbd subsystem, someone should fix those up one day before they hit a linking error.
--- drivers/block/drbd/drbd_int.h | 2 +- drivers/block/drbd/drbd_main.c | 4 ++-- drivers/block/drbd/drbd_nl.c | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-)
drivers/block/drbd/drbd_int.h +1 -1
@@ -75,7 +75,7 @@ extern int fault_rate;extern int fault_devs;#endif -extern char usermode_helper[]; +extern char drbd_usermode_helper[];/* This is used to stop/restart our threads.
drivers/block/drbd/drbd_main.c +2 -2
@@ -108,9 +108,9 @@ int proc_details; /* Detail level in proc drbd*//* Module parameter for setting the user mode helper program * to run. Default is /sbin/drbdadm */ -char usermode_helper[80 ] ="/sbin/drbdadm" ; +char drbd_usermode_helper[80 ] ="/sbin/drbdadm" ; -module_param_string (usermode_helper, usermode_helper,sizeof (usermode_helper),0644 ); +module_param_string (usermode_helper, drbd_usermode_helper,sizeof (drbd_usermode_helper),0644 );/* in 2.6.x, our device mapping and config info contains our virtual gendisks * as member "struct gendisk *vdisk;"
drivers/block/drbd/drbd_nl.c +10 -10
@@ -344,7 +344,7 @@ int drbd_khelper(struct drbd_device *device, char *cmd) (char [60 ]) { },/* address */ NULL };char mb[14 ]; -char *argv[] = {usermode_helper, cmd, mb,NULL }; +char *argv[] = {drbd_usermode_helper, cmd, mb,NULL };struct drbd_connection *connection =first_peer_device (device)->connection;struct sib_info sib;int ret;
@@ -359,19 +359,19 @@ int drbd_khelper(struct drbd_device *device, char *cmd)* write out any unsynced meta data changes now */ drbd_md_sync (device); -drbd_info (device,"helper command: %s %s %s\n" , usermode_helper, cmd, mb); +drbd_info (device,"helper command: %s %s %s\n" , drbd_usermode_helper, cmd, mb); sib.sib_reason =SIB_HELPER_PRE ; sib.helper_name = cmd;drbd_bcast_event (device, &sib);notify_helper (NOTIFY_CALL , device, connection, cmd,0 ); - ret =call_usermodehelper (usermode_helper, argv, envp,UMH_WAIT_PROC ); + ret =call_usermodehelper (drbd_usermode_helper, argv, envp,UMH_WAIT_PROC );if (ret)drbd_warn (device,"helper command: %s %s %s exit code %u (0x%x)\n" , - usermode_helper, cmd, mb, + drbd_usermode_helper, cmd, mb, (ret >>8 ) &0xff , ret);else drbd_info (device,"helper command: %s %s %s exit code %u (0x%x)\n" , - usermode_helper, cmd, mb, + drbd_usermode_helper, cmd, mb, (ret >>8 ) &0xff , ret); sib.sib_reason =SIB_HELPER_POST ; sib.helper_exit_code = ret;
@@ -396,24 +396,24 @@ enum drbd_peer_state conn_khelper(struct drbd_connection *connection, char *cmd) (char [60 ]) { },/* address */ NULL };char *resource_name = connection->resource->name; -char *argv[] = {usermode_helper, cmd, resource_name,NULL }; +char *argv[] = {drbd_usermode_helper, cmd, resource_name,NULL };int ret;setup_khelper_env (connection, envp);conn_md_sync (connection); -drbd_info (connection,"helper command: %s %s %s\n" , usermode_helper, cmd, resource_name); +drbd_info (connection,"helper command: %s %s %s\n" , drbd_usermode_helper, cmd, resource_name);/* TODO: conn_bcast_event() ?? */ notify_helper (NOTIFY_CALL ,NULL , connection, cmd,0 ); - ret =call_usermodehelper (usermode_helper, argv, envp,UMH_WAIT_PROC ); + ret =call_usermodehelper (drbd_usermode_helper, argv, envp,UMH_WAIT_PROC );if (ret)drbd_warn (connection,"helper command: %s %s %s exit code %u (0x%x)\n" , - usermode_helper, cmd, resource_name, + drbd_usermode_helper, cmd, resource_name, (ret >>8 ) &0xff , ret);else drbd_info (connection,"helper command: %s %s %s exit code %u (0x%x)\n" , - usermode_helper, cmd, resource_name, + drbd_usermode_helper, cmd, resource_name, (ret >>8 ) &0xff , ret);/* TODO: conn_bcast_event() ?? */ notify_helper (NOTIFY_RESPONSE ,NULL , connection, cmd, ret);
Signature · 1 line
2.10.2
Greg KH
maintainerPATCH 3/4 Make static usermode helper binaries constant
In reply to Greg KHThere are a number of usermode helper binaries that are "hard coded" in the kernel today, so mark them as "const" to make it harder for someone to change where the variables point to.
--- drivers/macintosh/windfarm_core.c | 2 +- drivers/net/hamradio/baycom_epp.c | 2 +- drivers/pnp/pnpbios/core.c | 5 +++-- drivers/staging/greybus/svc_watchdog.c | 4 ++-- drivers/staging/rtl8192e/rtl8192e/rtl_dm.c | 6 +++--- fs/nfsd/nfs4layouts.c | 6 ++++-- security/keys/request_key.c | 7 ++++--- 7 files changed, 18 insertions(+), 14 deletions(-)
drivers/macintosh/windfarm_core.c +1 -1
@@ -74,7 +74,7 @@ static inline void wf_notify(int event, void *param)static int wf_critical_overtemp (void ) { -static char * critical_overtemp_path ="/sbin/critical_overtemp" ; +static const char * critical_overtemp_path ="/sbin/critical_overtemp" ;char *argv[] = { critical_overtemp_path,NULL };static char *envp[] = {"HOME=/" ,"TERM=linux" ,
drivers/net/hamradio/baycom_epp.c +1 -1
@@ -299,7 +299,7 @@ static inline void baycom_int_freq(struct baycom_state *bc)* eppconfig_path should be setable via /proc/sys. */ -static char eppconfig_path[256 ] ="/usr/sbin/eppfpga" ; +static const char eppconfig_path[256 ] ="/usr/sbin/eppfpga" ;static char *envp[] = {"HOME=/" ,"TERM=linux" ,"PATH=/usr/bin:/bin" ,NULL };
drivers/pnp/pnpbios/core.c +3 -2
@@ -98,6 +98,7 @@ static struct completion unload_sem;*/ static int pnp_dock_event (int dock,struct pnp_docking_station_info *info) { +static const char *sbin_pnpbios ="/sbin/pnpbios" ;char *argv[3 ], **envp, *buf, *scratch;int i =0 , value;
@@ -112,7 +113,7 @@ static int pnp_dock_event(int dock, struct pnp_docking_station_info *info)* integrated into the driver core and use the usual infrastructure * like sysfs and uevents */ - argv[0 ] ="/sbin/pnpbios" ; + argv[0 ] = sbin_pnpbios; argv[1 ] ="dock" ; argv[2 ] =NULL ;
@@ -139,7 +140,7 @@ static int pnp_dock_event(int dock, struct pnp_docking_station_info *info) info->location_id, info->serial, info->capabilities); envp[i] =NULL ; - value =call_usermodehelper (argv [0 ], argv, envp,UMH_WAIT_EXEC ); + value =call_usermodehelper (sbin_pnpbios, argv, envp,UMH_WAIT_EXEC );kfree (buf);kfree (envp);return 0 ;
drivers/staging/greybus/svc_watchdog.c +2 -2
@@ -44,14 +44,14 @@ static int svc_watchdog_pm_notifier(struct notifier_block *notifier,static void greybus_reset (struct work_struct *work) { -static char start_path[256 ] ="/system/bin/start" ; +static const char start_path[256 ] ="/system/bin/start" ;static char *envp[] = {"HOME=/" ,"PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin" ,NULL , };static char *argv[] = { - start_path, + (char *)start_path,"unipro_reset" ,NULL , };
drivers/staging/rtl8192e/rtl8192e/rtl_dm.c +3 -3
@@ -268,7 +268,7 @@ void rtl92e_dm_watchdog(struct net_device *dev)static void _rtl92e_dm_check_ac_dc_power (struct net_device *dev) {struct r8192_priv *priv =rtllib_priv (dev); -static char *ac_dc_script ="/etc/acpi/wireless-rtl-ac-dc-power.sh" ; +static const char *ac_dc_script ="/etc/acpi/wireless-rtl-ac-dc-power.sh" ;char *argv[] = {ac_dc_script,DRV_NAME ,NULL };static char *envp[] = {"HOME=/" ,"TERM=linux" ,
@@ -1823,7 +1823,7 @@ static void _rtl92e_dm_check_rf_ctrl_gpio(void *data)enum rt_rf_power_state eRfPowerStateToSet;bool bActuallySet = false;char *argv[3 ]; -static char *RadioPowerPath ="/etc/acpi/events/RadioPower.sh" ; +static const char *RadioPowerPath ="/etc/acpi/events/RadioPower.sh" ;static char *envp[] = {"HOME=/" ,"TERM=linux" ,"PATH=/usr/bin:/bin" ,NULL };
@@ -1862,7 +1862,7 @@ static void _rtl92e_dm_check_rf_ctrl_gpio(void *data)else argv[1 ] ="RFON" ; - argv[0 ] = RadioPowerPath; + argv[0 ] = (char *)RadioPowerPath; argv[2 ] =NULL ;call_usermodehelper (RadioPowerPath, argv, envp,UMH_WAIT_PROC ); }
fs/nfsd/nfs4layouts.c +4 -2
@@ -613,6 +613,7 @@ nfsd4_cb_layout_fail(struct nfs4_layout_stateid *ls) {struct nfs4_client *clp = ls->ls_stid.sc_client;char addr_str[INET6_ADDRSTRLEN ]; +static const char *nfsd_recall_failed ="/sbin/nfsd-recall-failed" ;static char *envp[] = {"HOME=/" ,"TERM=linux" ,
@@ -628,12 +629,13 @@ nfsd4_cb_layout_fail(struct nfs4_layout_stateid *ls)"nfsd: client %s failed to respond to layout recall. " " Fencing..\n", addr_str); - argv[0 ] ="/sbin/nfsd-recall-failed" ; + argv[0 ] = (char *)nfsd_recall_failed; argv[1 ] = addr_str; argv[2 ] = ls->ls_file->f_path.mnt->mnt_sb->s_id; argv[3 ] =NULL ; - error =call_usermodehelper (argv[0 ], argv, envp,UMH_WAIT_PROC ); + error =call_usermodehelper (nfsd_recall_failed, argv, envp, +UMH_WAIT_PROC );if (error) {printk (KERN_ERR "nfsd: fence failed for client %s: %d!\n" , addr_str, error);
security/keys/request_key.c +4 -3
@@ -72,7 +72,7 @@ static void umh_keys_cleanup(struct subprocess_info *info)/* * Call a usermode helper with a specific session keyring. */ -static int call_usermodehelper_keys (char *path,char **argv,char **envp, +static int call_usermodehelper_keys (const char *path,char **argv,char **envp,struct key *session_keyring,int wait) {struct subprocess_info *info;
@@ -95,6 +95,7 @@ static int call_sbin_request_key(struct key_construction *cons, const char *op, void *aux) { +static const char *request_key ="/sbin/request-key" ;const struct cred *cred =current_cred ();key_serial_t prkey, sskey; struct key *key = cons->key, *authkey = cons->authkey, *keyring,
@@ -161,7 +162,7 @@ static int call_sbin_request_key(struct key_construction *cons,/* set up the argument list */ i =0 ; - argv[i++] ="/sbin/request-key" ; + argv[i++] = (char *)request_key; argv[i++] = (char *) op; argv[i++] = key_str; argv[i++] = uid_str;
@@ -172,7 +173,7 @@ static int call_sbin_request_key(struct key_construction *cons, argv[i] =NULL ;/* do it */ - ret =call_usermodehelper_keys (argv[0 ], argv, envp, keyring, + ret =call_usermodehelper_keys (request_key, argv, envp, keyring,UMH_WAIT_PROC );kdebug ("usermode -> 0x%x" , ret);if (ret >=0 ) {
Signature · 1 line
2.10.2
Greg KH
maintainerRFC 4/4 Introduce CONFIG_READONLY_USERMODEHELPER
In reply to Greg KHIf you can write to kernel memory, an "easy" way to get the kernel to run any application is to change the pointer of one of the usermode helper program names. To try to mitigate this, create a new config option, CONFIG_READONLY_USERMODEHELPER.
This option only allows "predefined" binaries to be called. A number of drivers and subsystems allow for the name of the binary to be changed, and this config option disables that capability, so be aware of that.
Note: Still a proof-of-concept at this point in time, doesn't cover all of the call_usermodehelper() calls just yet, including the "fun" of coredumps, it's still a work in progress.
--- arch/x86/kernel/cpu/mcheck/mce.c | 12 ++++++++---- drivers/block/drbd/drbd_int.h | 6 +++++- drivers/block/drbd/drbd_main.c | 5 +++++ drivers/video/fbdev/uvesafb.c | 19 ++++++++++++++----- fs/nfs/cache_lib.c | 12 ++++++++++-- include/linux/reboot.h | 2 ++ kernel/ksysfs.c | 6 +++++- kernel/reboot.c | 3 +++ kernel/sysctl.c | 4 ++++ lib/kobject_uevent.c | 3 +++ security/Kconfig | 17 +++++++++++++++++ 11 files changed, 76 insertions(+), 13 deletions(-)
arch/x86/kernel/cpu/mcheck/mce.c +8 -4
@@ -2337,15 +2337,16 @@ static ssize_t set_bank(struct device *s, struct device_attribute *attr, }static ssize_t -show_trigger (struct device *s,struct device_attribute *attr,char *buf) +trigger_show (struct device *s,struct device_attribute *attr,char *buf) {strcpy (buf, mce_helper);strcat (buf,"\n" );return strlen (mce_helper) +1 ; } -static ssize_t set_trigger (struct device *s,struct device_attribute *attr, -const char *buf,size_t siz) +#ifndef CONFIG_READONLY_USERMODEHELPER +static ssize_t trigger_store (struct device *s,struct device_attribute *attr, +const char *buf,size_t siz) {char *p;
@@ -2358,6 +2359,10 @@ static ssize_t set_trigger(struct device *s, struct device_attribute *attr,return strlen (mce_helper) + !!p; } +static DEVICE_ATTR_RW (trigger); +#else +static DEVICE_ATTR_RO (trigger); +#endif static ssize_t set_ignore_ce(struct device *s, struct device_attribute *attr,
@@ -2415,7 +2420,6 @@ static ssize_t store_int_with_restart(struct device *s,return ret; } -static DEVICE_ATTR(trigger, 0644, show_trigger, set_trigger); static DEVICE_INT_ATTR(tolerant, 0644, mca_cfg.tolerant); static DEVICE_INT_ATTR(monarch_timeout, 0644, mca_cfg.monarch_timeout); static DEVICE_BOOL_ATTR(dont_log_ce, 0644, mca_cfg.dont_log_ce);
drivers/block/drbd/drbd_int.h +5 -1
@@ -75,7 +75,11 @@ extern int fault_rate;extern int fault_devs;#endif -extern char drbd_usermode_helper[]; +extern +#ifdef CONFIG_READONLY_USERMODEHELPER + const +#endif +char drbd_usermode_helper[];/* This is used to stop/restart our threads.
drivers/block/drbd/drbd_main.c +5 -0
@@ -108,9 +108,14 @@ int proc_details; /* Detail level in proc drbd*//* Module parameter for setting the user mode helper program * to run. Default is /sbin/drbdadm */ +#ifdef CONFIG_READONLY_USERMODEHELPER +const +#endif char drbd_usermode_helper[80 ] ="/sbin/drbdadm" ; +#ifndef CONFIG_READONLY_USERMODEHELPER module_param_string (usermode_helper, drbd_usermode_helper,sizeof (drbd_usermode_helper),0644 ); +#endif /* in 2.6.x, our device mapping and config info contains our virtual gendisks * as member "struct gendisk *vdisk;"
drivers/video/fbdev/uvesafb.c +14 -5
@@ -30,7 +30,11 @@ static struct cb_id uvesafb_cn_id = { .idx = CN_IDX_V86D, .val = CN_VAL_V86D_UVESAFB }; +#ifdef CONFIG_READONLY_USERMODEHELPER +static const char v86d_path[PATH_MAX ] ="/sbin/v86d" ; +#else static char v86d_path[PATH_MAX ] ="/sbin/v86d" ; +#endif static char v86d_started;/* has v86d been started by uvesafb? */ static const struct fb_fix_screeninfo uvesafb_fix = {
@@ -114,7 +118,7 @@ static int uvesafb_helper_start(void) };char *argv[] = { - v86d_path, + (char *)v86d_path,NULL , };
@@ -1883,19 +1887,22 @@ static int uvesafb_setup(char *options) }#endif /* !MODULE */ -static ssize_t show_v86d (struct device_driver *dev,char *buf) +static ssize_t v86d_show (struct device_driver *dev,char *buf) {return snprintf (buf,PAGE_SIZE ,"%s\n" , v86d_path); } -static ssize_t store_v86d (struct device_driver *dev,const char *buf, +#ifndef CONFIG_READONLY_USERMODEHELPER +static ssize_t v86d_store (struct device_driver *dev,const char *buf,size_t count) {strncpy (v86d_path, buf,PATH_MAX );return count; } - -static DRIVER_ATTR(v86d, S_IRUGO | S_IWUSR, show_v86d, store_v86d); +static DRIVER_ATTR_RW (v86d); +#else +static DRIVER_ATTR_RO (v86d); +#endif static int uvesafb_init (void ) {
@@ -2017,8 +2024,10 @@ MODULE_PARM_DESC(mode_option,module_param (vbemode, ushort,0 );MODULE_PARM_DESC (vbemode,"VBE mode number to set, overrides the 'mode' option" ); +#ifndef CONFIG_READONLY_USERMODEHELPER module_param_string (v86d, v86d_path,PATH_MAX ,0660 );MODULE_PARM_DESC (v86d,"Path to the v86d userspace helper." ); +#endif MODULE_LICENSE ("GPL" );MODULE_AUTHOR ("Michal Januszewski <[email protected]>" );
fs/nfs/cache_lib.c +10 -2
@@ -20,13 +20,20 @@#define NFS_CACHE_UPCALL_PATHLEN 256#define NFS_CACHE_UPCALL_TIMEOUT 15 +#ifdef CONFIG_READONLY_USERMODEHELPER +static const char nfs_cache_getent_prog[NFS_CACHE_UPCALL_PATHLEN ] = +#else static char nfs_cache_getent_prog[NFS_CACHE_UPCALL_PATHLEN] = +#endif"/sbin/nfs_cache_getent" ;static unsigned long nfs_cache_getent_timeout =NFS_CACHE_UPCALL_TIMEOUT ; +#ifndef CONFIG_READONLY_USERMODEHELPER module_param_string (cache_getent, nfs_cache_getent_prog,sizeof (nfs_cache_getent_prog),0600 );MODULE_PARM_DESC (cache_getent,"Path to the client cache upcall program" ); +#endif +module_param_named (cache_getent_timeout, nfs_cache_getent_timeout, ulong,0600 );MODULE_PARM_DESC (cache_getent_timeout,"Timeout (in seconds) after which " "the cache upcall is assumed to have failed" );
@@ -39,7 +46,7 @@ int nfs_cache_upcall(struct cache_detail *cd, char *entry_name)NULL };char *argv[] = { - nfs_cache_getent_prog, + (char *)nfs_cache_getent_prog, cd->name, entry_name,NULL
@@ -48,7 +55,8 @@ int nfs_cache_upcall(struct cache_detail *cd, char *entry_name)if (nfs_cache_getent_prog[0 ] =='\0' ) goto out; - ret =call_usermodehelper (argv[0 ], argv, envp,UMH_WAIT_EXEC ); + ret =call_usermodehelper (nfs_cache_getent_prog, argv, envp, +UMH_WAIT_EXEC );/* * Disable the upcall mechanism if we're getting an ENOENT or * EACCES error. The admin can re-enable it on the fly by using
include/linux/reboot.h +2 -0
@@ -68,7 +68,9 @@ extern int C_A_D; /* for sysctl */void ctrl_alt_del (void );#define POWEROFF_CMD_PATH_LEN 256 +#ifndef CONFIG_READONLY_USERMODEHELPER extern char poweroff_cmd[POWEROFF_CMD_PATH_LEN ]; +#endif extern void orderly_poweroff (bool force);extern void orderly_reboot (void );
kernel/ksysfs.c +5 -1
@@ -44,6 +44,7 @@ static ssize_t uevent_helper_show(struct kobject *kobj, {return sprintf (buf,"%s\n" , uevent_helper); } +#ifndef CONFIG_READONLY_USERMODEHELPER static ssize_t uevent_helper_store (struct kobject *kobj,struct kobj_attribute *attr,const char *buf,size_t count)
@@ -57,7 +58,10 @@ static ssize_t uevent_helper_store(struct kobject *kobj,return count; }KERNEL_ATTR_RW (uevent_helper); -#endif +#else +KERNEL_ATTR_RO (uevent_helper); +#endif /* CONFIG_READONLY_USERMODEHELPER */ +#endif /* CONFIG_UEVENT_HELPER */ #ifdef CONFIG_PROFILING static ssize_t profiling_show(struct kobject *kobj,
kernel/reboot.c +3 -0
@@ -386,6 +386,9 @@ void ctrl_alt_del(void)kill_cad_pid (SIGINT ,1 ); } +#ifdef CONFIG_READONLY_USERMODEHELPER +static const +#endifchar poweroff_cmd[POWEROFF_CMD_PATH_LEN ] ="/sbin/poweroff" ;static const char reboot_cmd[] ="/sbin/reboot" ;
kernel/sysctl.c +4 -0
@@ -662,6 +662,7 @@ static struct ctl_table kern_table[] = { },#endif #ifdef CONFIG_UEVENT_HELPER +#ifndef CONFIG_READONLY_USERMODEHELPER { .procname = "hotplug", .data = &uevent_helper,
@@ -670,6 +671,7 @@ static struct ctl_table kern_table[] = { .proc_handler = proc_dostring, },#endif +#endif #ifdef CONFIG_CHR_DEV_SG { .procname = "sg-big-buff",
@@ -1079,6 +1081,7 @@ static struct ctl_table kern_table[] = { .proc_handler = proc_dointvec, },#endif +#ifndef CONFIG_READONLY_USERMODEHELPER { .procname = "poweroff_cmd", .data = &poweroff_cmd,
@@ -1086,6 +1089,7 @@ static struct ctl_table kern_table[] = { .mode = 0644, .proc_handler = proc_dostring, }, +#endif #ifdef CONFIG_KEYS { .procname = "keys",
lib/kobject_uevent.c +3 -0
@@ -29,6 +29,9 @@u64 uevent_seqnum;#ifdef CONFIG_UEVENT_HELPER +#ifdef CONFIG_READONLY_USERMODEHELPER +const +#endif char uevent_helper[UEVENT_HELPER_PATH_LEN ] =CONFIG_UEVENT_HELPER_PATH ;#endif #ifdef CONFIG_NET
security/Kconfig +17 -0
@@ -158,6 +158,23 @@ config HARDENED_USERCOPY_PAGESPAN been removed. This config is intended to be used only while trying to find such users. +config READONLY_USERMODEHELPER + bool "Make User Mode Helper program names read-only" + default N + help + Some user mode helper program names can be changed at runtime + by userspace programs. Prevent this from happening by "hard + coding" all user mode helper program names at kernel build + time, moving the names into read-only memory, making it harder + for any arbritrary program to be run as root if something were + to go wrong. + + Note, some subsystems and drivers allow their user mode helper + binary to be changed with a module parameter, sysctl, sysfs + file, or some combination of these. Enabling this option + prevents the binary name to be changed, which might not be + good for some systems. + source security/selinux/Kconfig source security/smack/Kconfig source security/tomoyo/Kconfig
Signature · 1 line
2.10.2
Greg KH
maintainerRe: [kernel-hardening] [PATCH 3/4] Make static usermode helper binaries constant
In reply to Greg KHQuoted 21 lines · Greg KH
There are a number of usermode helper binaries that are "hard coded" in the kernel today, so mark them as "const" to make it harder for someone to change where the variables point to.
Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/macintosh/windfarm_core.c | 2 +- drivers/net/hamradio/baycom_epp.c | 2 +- drivers/pnp/pnpbios/core.c | 5 +++-- drivers/staging/greybus/svc_watchdog.c | 4 ++-- drivers/staging/rtl8192e/rtl8192e/rtl_dm.c | 6 +++--- fs/nfsd/nfs4layouts.c | 6 ++++-- security/keys/request_key.c | 7 ++++--- 7 files changed, 18 insertions(+), 14 deletions(-)drivers/macintosh/windfarm_core.c +0
-0
drivers/macintosh/windfarm_core.c static inline void wf_notify(int event, void *param) · line 80
Quoted 8 lines
drivers/macintosh/windfarm_core.c +1
-1@@ -74,7 +74,7 @@ static inline void wf_notify(int event, void *param)static int wf_critical_overtemp (void ) { -static char * critical_overtemp_path ="/sbin/critical_overtemp" ; +static const char * critical_overtemp_path ="/sbin/critical_overtemp" ;char *argv[] = { critical_overtemp_path,NULL };static char *envp[] = {"HOME=/" ,"TERM=linux" ,
Doh, minor compiler warnings for this one, and others in this patch, I'll fix them up...
Hi,
Quoted 5 lines · Greg KH
The issue is that if you end up getting write access to kernel memory, if you change the string '
/sbin/hotplug' to point to '/home/hacked/my_binary', then the next uevent that the system makes will call this binary instead of the "trusted" one.
Quoted 8 lines
It does this by moving the location of the binary to be in read-only memory. This works for a number of
call_usermodehelperstrings, as they are specified at build or configuration time. But, some subsystems have the option to let userspace change the value at runtime, so those values can't live in read-only memory.So, anyone have any better ideas? Is this approach worth it? Or should we just go down the "whitelist" path?
As a general note, I believe the write-rarely / mostly-ro [1] stuff is meant to cater for this case, but I haven't heard anything on that front recently (and there doesn't appear to be anything on the KSPP TODO page).
If that does cater for this case, and if we're able to implement that generically, that might be nicer than locking down the set of binaries at build time.
Chen, are you still looking at implementing write-rarely support?
Thanks,
Mark.
[1] http://www.openwall.com/lists/kernel-hardening/2016/11/16/3
Quoted 15 lines · Mark Rutland
Hi,
Quoted 10 lines · Greg KH
The issue is that if you end up getting write access to kernel memory, if you change the string '
/sbin/hotplug' to point to '/home/hacked/my_binary', then the next uevent that the system makes will call this binary instead of the "trusted" one.It does this by moving the location of the binary to be in read-only memory. This works for a number of
call_usermodehelperstrings, as they are specified at build or configuration time. But, some subsystems have the option to let userspace change the value at runtime, so those values can't live in read-only memory.
Quoted 7 lines
Quoted 2 lines
So, anyone have any better ideas? Is this approach worth it? Or should we just go down the "whitelist" path?
As a general note, I believe the write-rarely / mostly-ro [1] stuff is meant to cater for this case, but I haven't heard anything on that front recently (and there doesn't appear to be anything on the KSPP TODO page).
Using write-rarely on sysctls makes sense, though I remain concerned about userspace bugs where root gets tricked into writing a bad value into a sysctl (which write-rarely wouldn't be able to help). A CONFIG here seems okay without the write-rarely infrastructure, though I wonder if a write-once runtime value would be better? Something like modules_disabled where once flipped, the sysctls become read-only?
Quoted 10 lines
If that does cater for this case, and if we're able to implement that generically, that might be nicer than locking down the set of binaries at build time.
Chen, are you still looking at implementing write-rarely support?
Thanks,
Mark.[1] http://www.openwall.com/lists/kernel-hardening/2016/11/16/3
Even if it's "wrong", I'd love to see an actual RFC for the write-rarely. In the face of a "wrong" patch, we can at least more forward with alternative ideas...
-Kees
Signature · 2 lines
Kees Cook Nexus Security
Re: [kernel-hardening] [PATCH 3/4] Make static usermode helper binaries constant
In reply to Greg KHQuoted 4 lines · Greg KH
There are a number of usermode helper binaries that are "hard coded" in the kernel today, so mark them as "const" to make it harder for someone to change where the variables point to.
You're not preventing change of where they point to, but rather preventing modification of the pointed-to data through these pointers...
Quoted 16 lines
Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/macintosh/windfarm_core.c | 2 +- drivers/net/hamradio/baycom_epp.c | 2 +- drivers/pnp/pnpbios/core.c | 5 +++-- drivers/staging/greybus/svc_watchdog.c | 4 ++-- drivers/staging/rtl8192e/rtl8192e/rtl_dm.c | 6 +++--- fs/nfsd/nfs4layouts.c | 6 ++++-- security/keys/request_key.c | 7 ++++--- 7 files changed, 18 insertions(+), 14 deletions(-)drivers/macintosh/windfarm_core.c +0
-0
drivers/macintosh/windfarm_core.c static inline void wf_notify(int event, void *param) · line 77
Quoted 5 lines
drivers/macintosh/windfarm_core.c +1
-1@@ -74,7 +74,7 @@ static inline void wf_notify(int event, void *param)static int wf_critical_overtemp (void ) { -static char * critical_overtemp_path ="/sbin/critical_overtemp" ; +static const char * critical_overtemp_path ="/sbin/critical_overtemp" ;
Should be static char *const critical_overtemp_path, or if you prefer static const char *const critical_overtemp_path (since the pointed-to data is not modifiable either). Likewise elsewhere.
Rich
Re: [RFC 4/4] Introduce CONFIG_READONLY_USERMODEHELPER
In reply to Greg KHQuoted 33 lines · Greg KH
If you can write to kernel memory, an "easy" way to get the kernel to run any application is to change the pointer of one of the usermode helper program names. To try to mitigate this, create a new config option,
CONFIG_READONLY_USERMODEHELPER.This option only allows "predefined" binaries to be called. A number of drivers and subsystems allow for the name of the binary to be changed, and this config option disables that capability, so be aware of that.
Note: Still a proof-of-concept at this point in time, doesn't cover all of the
call_usermodehelper()calls just yet, including the "fun" of coredumps, it's still a work in progress.Not-Signed-off-by Greg Kroah-Hartman <[email protected]>--- arch/x86/kernel/cpu/mcheck/mce.c | 12 ++++++++---- drivers/block/drbd/drbd_int.h | 6 +++++- drivers/block/drbd/drbd_main.c | 5 +++++ drivers/video/fbdev/uvesafb.c | 19 ++++++++++++++----- fs/nfs/cache_lib.c | 12 ++++++++++-- include/linux/reboot.h | 2 ++ kernel/ksysfs.c | 6 +++++- kernel/reboot.c | 3 +++ kernel/sysctl.c | 4 ++++ lib/kobject_uevent.c | 3 +++ security/Kconfig | 17 +++++++++++++++++ 11 files changed, 76 insertions(+), 13 deletions(-)arch/x86/kernel/cpu/mcheck/mce.c +0
-0
arch/x86/kernel/cpu/mcheck/mce.c static ssize_t set_bank(struct device *s, struct device_attribute *attr, · line 2344
Quoted 9 lines
arch/x86/kernel/cpu/mcheck/mce.c +1
-1@@ -2337,15 +2337,16 @@ static ssize_t set_bank(struct device *s, struct device_attribute *attr, }static ssize_t -show_trigger (struct device *s,struct device_attribute *attr,char *buf) +trigger_show (struct device *s,struct device_attribute *attr,char *buf) {strcpy (buf, mce_helper);strcat (buf,"\n" );return strlen (mce_helper) +1 ;
The +1 is wrong, AFAICT. Also, is speed really needed here?
return scnprintf(buf, PAGE_SIZE, "%s\n", mce_helper);
is more readable...
Quoted 32 lines
arch/x86/kernel/cpu/mcheck/mce.c +7
-3-static ssize_t set_trigger (struct device *s,struct device_attribute *attr, -const char *buf,size_t siz) +#ifndef CONFIG_READONLY_USERMODEHELPER +static ssize_t trigger_store (struct device *s,struct device_attribute *attr, +const char *buf,size_t siz) {char *p;@@ -2358,6 +2359,10 @@ static ssize_t set_trigger(struct device *s, struct device_attribute *attr,return strlen (mce_helper) + !!p; } +static DEVICE_ATTR_RW (trigger); +#else +static DEVICE_ATTR_RO (trigger); +#endif static ssize_t set_ignore_ce(struct device *s, struct device_attribute *attr,@@ -2415,7 +2420,6 @@ static ssize_t store_int_with_restart(struct device *s,return ret; } -static DEVICE_ATTR(trigger, 0644, show_trigger, set_trigger); static DEVICE_INT_ATTR(tolerant, 0644, mca_cfg.tolerant); static DEVICE_INT_ATTR(monarch_timeout, 0644, mca_cfg.monarch_timeout); static DEVICE_BOOL_ATTR(dont_log_ce, 0644, mca_cfg.dont_log_ce);drivers/block/drbd/drbd_int.h +0
-0
drivers/block/drbd/drbd_int.h extern int fault_rate; · line 82
Quoted 9 lines
drivers/block/drbd/drbd_int.h +5
-1@@ -75,7 +75,11 @@ extern int fault_rate;extern int fault_devs;#endif -extern char drbd_usermode_helper[]; +extern +#ifdef CONFIG_READONLY_USERMODEHELPER + const +#endif +char drbd_usermode_helper[];
This #ifdef; const; #endif is repeated a few times. Perhaps better to create a separate macro:
#ifdef CONFIG_READONLY_USERMODEHELPER
# define __ro_umh const #else # define __ro_umh /**/ #endif
...
extern __ro_umh char drbd_usermode_helper[];
-Kees
Signature · 2 lines
Kees Cook Nexus Security
Re: [kernel-hardening] [PATCH 3/4] Make static usermode helper binaries constant
In reply to Rich FelkerQuoted 27 lines · Rich Felker
Quoted 4 lines · Greg KH
There are a number of usermode helper binaries that are "hard coded" in the kernel today, so mark them as "const" to make it harder for someone to change where the variables point to.
You're not preventing change of where they point to, but rather preventing modification of the pointed-to data through these pointers...
Quoted 17 lines
Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/macintosh/windfarm_core.c | 2 +- drivers/net/hamradio/baycom_epp.c | 2 +- drivers/pnp/pnpbios/core.c | 5 +++-- drivers/staging/greybus/svc_watchdog.c | 4 ++-- drivers/staging/rtl8192e/rtl8192e/rtl_dm.c | 6 +++--- fs/nfsd/nfs4layouts.c | 6 ++++-- security/keys/request_key.c | 7 ++++--- 7 files changed, 18 insertions(+), 14 deletions(-)drivers/macintosh/windfarm_core.c +0
-0@@ -74,7 +74,7 @@ static inline void wf_notify(int event, void *param)
Quoted 8 lines
drivers/macintosh/windfarm_core.c static inline void wf_notify(int event, void *param) · line 77
Quoted 4 lines
drivers/macintosh/windfarm_core.c +1
-1static int wf_critical_overtemp (void ) { -static char * critical_overtemp_path ="/sbin/critical_overtemp" ; +static const char * critical_overtemp_path ="/sbin/critical_overtemp" ;Should be static char *const
critical_overtemp_path, or if you prefer static const char *constcritical_overtemp_path(since the pointed-to data is not modifiable either). Likewise elsewhere.
argh, ok, I failed here, thanks for that, that's what I get for writing code on an airplane...
let me rework this, I also want to make argv and env static too, just for good measure.
thanks,
greg k-h
Greg KH
maintainerRe: [RFC 4/4] Introduce CONFIG_READONLY_USERMODEHELPER
In reply to Kees CookQuoted 36 lines · Kees Cook
Quoted 35 lines · Greg KH
If you can write to kernel memory, an "easy" way to get the kernel to run any application is to change the pointer of one of the usermode helper program names. To try to mitigate this, create a new config option,
CONFIG_READONLY_USERMODEHELPER.This option only allows "predefined" binaries to be called. A number of drivers and subsystems allow for the name of the binary to be changed, and this config option disables that capability, so be aware of that.
Note: Still a proof-of-concept at this point in time, doesn't cover all of the
call_usermodehelper()calls just yet, including the "fun" of coredumps, it's still a work in progress.Not-Signed-off-by Greg Kroah-Hartman <[email protected]>--- arch/x86/kernel/cpu/mcheck/mce.c | 12 ++++++++---- drivers/block/drbd/drbd_int.h | 6 +++++- drivers/block/drbd/drbd_main.c | 5 +++++ drivers/video/fbdev/uvesafb.c | 19 ++++++++++++++----- fs/nfs/cache_lib.c | 12 ++++++++++-- include/linux/reboot.h | 2 ++ kernel/ksysfs.c | 6 +++++- kernel/reboot.c | 3 +++ kernel/sysctl.c | 4 ++++ lib/kobject_uevent.c | 3 +++ security/Kconfig | 17 +++++++++++++++++ 11 files changed, 76 insertions(+), 13 deletions(-)arch/x86/kernel/cpu/mcheck/mce.c +0
-0@@ -2337,15 +2337,16 @@ static ssize_t set_bank(struct device *s, struct device_attribute *attr, }
Quoted 9 lines
arch/x86/kernel/cpu/mcheck/mce.c static ssize_t set_bank(struct device *s, struct device_attribute *attr, · line 2344
Quoted 7 lines
arch/x86/kernel/cpu/mcheck/mce.c +1
-1static ssize_t -show_trigger (struct device *s,struct device_attribute *attr,char *buf) +trigger_show (struct device *s,struct device_attribute *attr,char *buf) {strcpy (buf, mce_helper);strcat (buf,"\n" );return strlen (mce_helper) +1 ;The +1 is wrong, AFAICT. Also, is speed really needed here?
No, this is sysfs files, no speed at all :)
Quoted 3 lines
return scnprintf(buf, PAGE_SIZE, "%s\n", mce_helper);is more readable...
true, that's nicer, I was trying not to change things that I didn't have to.
Quoted 46 lines
Quoted 32 lines
arch/x86/kernel/cpu/mcheck/mce.c +7
-3-static ssize_t set_trigger (struct device *s,struct device_attribute *attr, -const char *buf,size_t siz) +#ifndef CONFIG_READONLY_USERMODEHELPER +static ssize_t trigger_store (struct device *s,struct device_attribute *attr, +const char *buf,size_t siz) {char *p;@@ -2358,6 +2359,10 @@ static ssize_t set_trigger(struct device *s, struct device_attribute *attr,return strlen (mce_helper) + !!p; } +static DEVICE_ATTR_RW (trigger); +#else +static DEVICE_ATTR_RO (trigger); +#endif static ssize_t set_ignore_ce(struct device *s, struct device_attribute *attr,@@ -2415,7 +2420,6 @@ static ssize_t store_int_with_restart(struct device *s,return ret; } -static DEVICE_ATTR(trigger, 0644, show_trigger, set_trigger); static DEVICE_INT_ATTR(tolerant, 0644, mca_cfg.tolerant); static DEVICE_INT_ATTR(monarch_timeout, 0644, mca_cfg.monarch_timeout); static DEVICE_BOOL_ATTR(dont_log_ce, 0644, mca_cfg.dont_log_ce);drivers/block/drbd/drbd_int.h +0
-0drivers/block/drbd/drbd_int.h extern int fault_rate; · line 82
Quoted 9 lines
drivers/block/drbd/drbd_int.h +5
-1@@ -75,7 +75,11 @@ extern int fault_rate;extern int fault_devs;#endif -extern char drbd_usermode_helper[]; +extern +#ifdef CONFIG_READONLY_USERMODEHELPER + const +#endif +char drbd_usermode_helper[];This #ifdef; const; #endif is repeated a few times. Perhaps better to create a separate macro:
#ifdef
CONFIG_READONLY_USERMODEHELPER
Quoted 8 lines
# define __ro_umh const #else # define __ro_umh /**/ #endif...
extern
__ro_umhchar drbd_usermode_helper[];
Ah, much cleaner, thanks, I'll go do something like that. After fixing up the static const crap I got wrong...
greg k-h
Hi Greg,
Quoted 2 lines · Greg KH
So, anyone have any better ideas? Is this approach worth it? Or should we just go down the "whitelist" path?
I think your approach is generally better than the whitelist path. But maybe there's yet a third approach that involves futzing with page permissions at runtime. I think grsec does something similar with read_mostly function pointer structs. Namely, they make them read-only const, and then temporarily twiddle the page permissions if it needs to be changed while disabling preemption. There could be a particular class of data that needs to be "opened" and "closed" in order to modify. Seems like these strings would be a good use of that.
Jason
Greg Kroah-Hartman
maintainerQuoted 6 lines · Jason A. Donenfeld
Hi Greg,
Quoted 2 lines · Greg KH
So, anyone have any better ideas? Is this approach worth it? Or should we just go down the "whitelist" path?
Quoted 8 lines
I think your approach is generally better than the whitelist path. But maybe there's yet a third approach that involves futzing with page permissions at runtime. I think grsec does something similar with
read_mostlyfunction pointer structs. Namely, they make them read-only const, and then temporarily twiddle the page permissions if it needs to be changed while disabling preemption. There could be a particular class of data that needs to be "opened" and "closed" in order to modify. Seems like these strings would be a good use of that.
Yes, but that's a much larger issue and if that feature ever lands, we can switch these strings over to that functionality.
thanks,
greg k-h
Re: [kernel-hardening] [PATCH 3/4] Make static usermode helper binaries constant
In reply to Greg KHQuoted 37 lines · Greg KH
Quoted 27 lines · Rich Felker
Quoted 4 lines · Greg KH
There are a number of usermode helper binaries that are "hard coded" in the kernel today, so mark them as "const" to make it harder for someone to change where the variables point to.
You're not preventing change of where they point to, but rather preventing modification of the pointed-to data through these pointers...
Quoted 17 lines
Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/macintosh/windfarm_core.c | 2 +- drivers/net/hamradio/baycom_epp.c | 2 +- drivers/pnp/pnpbios/core.c | 5 +++-- drivers/staging/greybus/svc_watchdog.c | 4 ++-- drivers/staging/rtl8192e/rtl8192e/rtl_dm.c | 6 +++--- fs/nfsd/nfs4layouts.c | 6 ++++-- security/keys/request_key.c | 7 ++++--- 7 files changed, 18 insertions(+), 14 deletions(-)drivers/macintosh/windfarm_core.c +0
-0@@ -74,7 +74,7 @@ static inline void wf_notify(int event, void *param)Quoted 8 lines
drivers/macintosh/windfarm_core.c static inline void wf_notify(int event, void *param) · line 77
Quoted 4 lines
drivers/macintosh/windfarm_core.c +1
-1static int wf_critical_overtemp (void ) { -static char * critical_overtemp_path ="/sbin/critical_overtemp" ; +static const char * critical_overtemp_path ="/sbin/critical_overtemp" ;Should be static char *const
critical_overtemp_path, or if you prefer static const char *constcritical_overtemp_path(since the pointed-to data is not modifiable either). Likewise elsewhere.
Quoted 5 lines
argh, ok, I failed here, thanks for that, that's what I get for writing code on an airplane...
let me rework this, I also want to make argv and env static too, just for good measure.
To follow up on this, and after staring at too many outputs of the compiler, I think what this really should be is:
static char const critical_overtemp_path[] = "/sbin/critical_overtemp";
right?
That way both the variable, and the data, end up in read-only memory from what I can tell.
But, if I do:
static char const char critical_overtemp_path[] = "/sbin/critical_overtemp";
then sparse complains to me about:
warning: duplicate const
Is that just sparse being dense, or is the latter one really better here? It seems that both of the above put the data and variable into the same segment (.rodata).
thanks,
greg k-h
Re: [kernel-hardening] [PATCH 3/4] Make static usermode helper binaries constant
Quoted 15 lines
To follow up on this, and after staring at too many outputs of the compiler, I think what this really should be is:
static char const critical_overtemp_path[] ="
/sbin/critical_overtemp";
right?That way both the variable, and the data, end up in read-only memory from what I can tell.
But, if I do:
static char const char critical_overtemp_path[] ="
/sbin/critical_overtemp";
then sparse complains to me about:
warning: duplicate const
Quoted 7 lines
Is that just sparse being dense, or is the latter one really better here? It seems that both of the above put the data and variable into the same segment (.rodata).
thanks,
greg k-h
Either 'char *const foo = "bar"' or 'const char *const foo = "bar" will also be a string constant in rodata with a pointer in rodata referring to them. Duplicate string constants get merged without any analysis as there's no guarantee of a unique address for the data itself since it's not a variable. 'const char foo[] = "bar"' goes into rodata too, but is the toolchain can't assume it can't safely merge strings + sizeof works but gcc/clang know how to optimize constant strlen anyway.
The 'const' qualifier for pointers doesn't really do anything, it's when it's used on the variable (after the pointer) that it can do more than acting as a programming guide.
Greg KH
maintainerRe: [kernel-hardening] [PATCH 3/4] Make static usermode helper binaries constant
Quoted 23 lines · Daniel Micay
Quoted 15 lines
To follow up on this, and after staring at too many outputs of the compiler, I think what this really should be is:
static char const critical_overtemp_path[] ="
/sbin/critical_overtemp";
right?That way both the variable, and the data, end up in read-only memory from what I can tell.
But, if I do:
static char const char critical_overtemp_path[] ="
/sbin/critical_overtemp";
then sparse complains to me about:
warning: duplicate constQuoted 7 lines
Is that just sparse being dense, or is the latter one really better here? It seems that both of the above put the data and variable into the same segment (.rodata).
thanks,
greg k-h
Quoted 7 lines
Either 'char *const foo = "bar"' or 'const char *const foo = "bar" will also be a string constant in rodata with a pointer in rodata referring to them. Duplicate string constants get merged without any analysis as there's no guarantee of a unique address for the data itself since it's not a variable. 'const char foo[] = "bar"' goes into rodata too, but is the toolchain can't assume it can't safely merge strings + sizeof works but gcc/clang know how to optimize constant strlen anyway.
Thanks for the explanation. I don't think we need to worry about merging these strings, but I'll keep it in mind.
However, the "folklore" of the kernel was to never do:
char *foo = "bar"; but instead do: char foo[] = "bar";
to save on the extra variable that the former creates. Is that no longer the case and we really should be using '*' to allow gcc to be smarter about optimizations?
Quoted 3 lines
The 'const' qualifier for pointers doesn't really do anything, it's when it's used on the variable (after the pointer) that it can do more than acting as a programming guide.
Many thanks for the explanations,
greg k-h
Re: [kernel-hardening] [PATCH 3/4] Make static usermode helper binaries constant
Quoted 17 lines
Thanks for the explanation. I don't think we need to worry about merging these strings, but I'll keep it in mind.
However, the "folklore" of the kernel was to never do:
char *foo = "bar"; but instead do: char foo[] = "bar";to save on the extra variable that the former creates. Is that no longer the case and we really should be using '*' to allow gcc to be smarter about optimizations?
Quoted 5 lines
The 'const' qualifier for pointers doesn't really do anything, it's when
it's used on the variable (after the pointer) that it can do more than
acting as a programming guide.
Quoted 3 lines
Many thanks for the explanations,
greg k-h
Can see what the compiler has to work with pretty easily from LLVM IR:
char *const constant_string_constant = "string";
char *const constant_string_constant2 = "string";
char *non_constant_string_constant = "string";
char *non_constant_string_constant2 = "string";
char non_constant_string_array[] = "string";
char non_constant_string_array2[] = "string";
const char constant_string_array[] = "string";
const char constant_string_array2[] = "string";Becomes:
@.str = private unnamed_addr constant [7 x i8] c"string\00", align 1
@constant_string_constant = constant i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), align 8
@constant_string_constant2 = constant i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), align 8
@non_constant_string_constant = global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), align 8
@non_constant_string_constant2 = global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), align 8
@non_constant_string_array = global [7 x i8] c"string\00", align 1
@non_constant_string_array2 = global [7 x i8] c"string\00", align 1
@constant_string_array = constant [7 x i8] c"string\00", align 1
@constant_string_array2 = constant [7 x i8] c"string\00", align 1And with optimization:
@constant_string_constant = local_unnamed_addr constant i8* getelementptr inbounds ([7 x i8], [7 x i8]* @constant_string_array, i64 0, i64 0), align 8 @constant_string_constant2 = local_unnamed_addr constant i8* getelementptr inbounds ([7 x i8], [7 x i8]* @constant_string_array, i64 0, i64 0), align 8 @non_constant_string_constant = local_unnamed_addr global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @constant_string_array, i64 0, i64 0), align 8 @non_constant_string_constant2 = local_unnamed_addr global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @constant_string_array, i64 0, i64 0), align 8 @non_constant_string_array = local_unnamed_addr global [7 x i8] c"string\00", align 1
@non_constant_string_array2 = local_unnamed_addr global [7 x i8] c"string\00", align 1
@constant_string_array = local_unnamed_addr constant [7 x i8] c"string\00", align 1
@constant_string_array2 = local_unnamed_addr constant [7 x i8] c"string\00", align 1
If they're static though, the compiler can see that nothing takes the address (local_unnamed_addr == unnamed_addr if it's internal) so it doesn't need separate variables anyway:
static char *const constant_string_constant = "string";
static char *const constant_string_constant2 = "string";
char *foo() {
return constant_string_constant;
}
char *bar() {
return constant_string_constant2;
}Becomes (with optimization):
@.str = private unnamed_addr constant [7 x i8] c"string\00", align 1
; Function Attrs: norecurse nounwind readnone uwtable
define i8* @foo() local_unnamed_addr #0 {
ret i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i64 0, i64 0)
}
; Function Attrs: norecurse nounwind readnone uwtable
define i8* @bar() local_unnamed_addr #0 {
ret i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i64 0, i64 0)
}
So for statics, I think static const char * wins due to allowing merging (although it doesn't matter here). For non-statics, you end up with extra pointer constants. Those could get removed, but Linux doesn't have -fvisibility=hidden and I'm not sure how clever linkers are. Maybe setting up -fvisibility=hidden to work with monolithic non-module- enabled builds could actually be realistic. Expect it'd remove a fair bit of bloat but not sure how much would need to be marked as non-hidden other than the userspace ABI.
Re: [kernel-hardening] [PATCH 3/4] Make static usermode helper binaries constant
Quoted 11 lines
So for statics, I think
static const char *wins due to allowing merging (although it doesn't matter here). For non-statics, you end up with extra pointer constants. Those could get removed, but Linux doesn't
have -fvisibility=hidden and I'm not sure how clever linkers are. Maybe
setting up -fvisibility=hidden to work with monolithic non-module- enabled builds could actually be realistic. Expect it'd remove a fair bit of bloat but not sure how much would need to be marked as non- hidden
other than the userspace ABI.
-fvisibility=hidden + LTO would be really awesome though, since that doesn't depend on the cleverness of linkers. So much that could be ripped out of real world monolithic builds. Kinda getting off-topic now though. LTO is pretty scary from a security perspective due to how much worse undefined behavior that was previously harmless can get.
Quoted 10 lines · Greg KH
Hi all,
Here's a proof-of-concept patch series that tries to work to address the issue of
call_usermodehelperbeing abused to have the kernel call any userspace binary with full root permissions.The issue is that if you end up getting write access to kernel memory, if you change the string '
/sbin/hotplug' to point to '/home/hacked/my_binary', then the next uevent that the system makes will call this binary instead of the "trusted" one.
You seem to be targeting a situation where the kernel memory can be easily changed, but filesystem content cannot (if it could - the attacker would simply replace /sbin/hotplug).
If that is a credible threat scenario, it seems to me that the simplest mitigation is to have call_usermodehelper always call a single compiled-in path - e.g. /sbin/usermode-helper - and rely on that program to validate argv[0] and call it if it is deemed safe.
i.e. get the policy out of the kernel.
Just a thought,
NeilBrown
Greg KH
maintainerQuoted 8 lines · NeilBrown
Quoted 6 lines · Greg KH
Hi all,
Here's a proof-of-concept patch series that tries to work to address the issue of
call_usermodehelperbeing abused to have the kernel call any userspace binary with full root permissions.
Quoted 8 lines
Quoted 4 lines
The issue is that if you end up getting write access to kernel memory, if you change the string '
/sbin/hotplug' to point to '/home/hacked/my_binary', then the next uevent that the system makes will call this binary instead of the "trusted" one.You seem to be targeting a situation where the kernel memory can be easily changed, but filesystem content cannot (if it could - the attacker would simply replace
/sbin/hotplug).
Correct, like an embedded system with a read-only system partition, or for when some kernel bug allows for random memory writes, yet privilege escalation is hard to achieve for your process.
Quoted 6 lines
If that is a credible threat scenario, it seems to me that the simplest mitigation is to have
call_usermodehelperalways call a single compiled-in path - e.g./sbin/usermode-helper- and rely on that program to validate argv[0] and call it if it is deemed safe.i.e. get the policy out of the kernel.
Ah, that's a nice idea. It's one step more flexible than the "just disable usermodehelper entirely", which is what I was going to do after this all got reworked, while still allowing a system that relied on only one or two of these usermodehelper apps to still operate normally. And it allows for all of those future users of the api to not have to be manually audited. Punting the issue to userspace is something I always love to do :)
I'll try that out on an Android system after the holiday break to see how feasible that would be, thanks for the idea!
greg k-h
Quoted 7 lines · Greg KH
Quoted 3 lines
You seem to be targeting a situation where the kernel memory can be easily changed, but filesystem content cannot (if it could - the attacker would simply replace
/sbin/hotplug).Correct, like an embedded system with a read-only system partition, or for when some kernel bug allows for random memory writes, yet privilege escalation is hard to achieve for your process.
Sorry, I really don't get this.
If kernel memory can be easily changed (which is assumed here), why bother with all this? I'll just set current->uid to 0 and be done.
Signature · 2 lines
Jiri Kosina SUSE Labs
Greg KH
maintainerQuoted 6 lines · Jiri Kosina
Quoted 4 lines · Greg KH
Quoted 3 lines
You seem to be targeting a situation where the kernel memory can be easily changed, but filesystem content cannot (if it could - the attacker would simply replace
/sbin/hotplug).
Quoted 8 lines
Quoted 3 lines
Correct, like an embedded system with a read-only system partition, or for when some kernel bug allows for random memory writes, yet privilege escalation is hard to achieve for your process.
Sorry, I really don't get this.
If kernel memory can be easily changed (which is assumed here), why bother with all this? I'll just set
current->uidto 0 and be done.
Because you don't want your current process to uid 0, you want some other program to run as root. It's quite common for exploits to work this way, take a look at how the p0wn-to-own "contests" usually break out of sandboxed systems like browsers.
thanks,
greg k-h
Quoted 9 lines · Greg KH
Quoted 4 lines
Sorry, I really don't get this.
If kernel memory can be easily changed (which is assumed here), why bother with all this? I'll just set
current->uidto 0 and be done.Because you don't want your current process to uid 0, you want some other program to run as root. It's quite common for exploits to work this way, take a look at how the p0wn-to-own "contests" usually break out of sandboxed systems like browsers.
So what kind of sandbox are we talking about here?
namespaces-based sanbox? If you have direct access to kernel memory, you can just assign whatever context you want to task_struct's fs struct, and you are out of a sandbox.
chroot-based sandbox? Exactly the same argument (you just reset fs->root in task_struct).
I stay totally unconvinced that such kind of countermeasure brings any value whatsoever. Could you please bring up a particular usecase, where you have complete control over kernel memory, and still the only possible exploit factor is redirecting usermodhelper? It feels like rather random shot into darkness.
Thanks,
Signature · 2 lines
Jiri Kosina SUSE Labs
Quoted 5 lines · Jiri Kosina
I stay totally unconvinced that such kind of countermeasure brings any value whatsoever. Could you please bring up a particular usecase, where you have complete control over kernel memory, and still the only possible exploit factor is redirecting usermodhelper? It feels like rather random shot into darkness.
If we want to make usermod helper really secure, perhaps the best way to go would be to completely nuke it and handle everyhting in udev; that'd be quite some work though, especially so that we don't break all the corner cases of module autoloading (request_module() and such).
Thanks,
Signature · 2 lines
Jiri Kosina SUSE Labs
Greg KH
maintainerQuoted 8 lines · Jiri Kosina
Quoted 5 lines · Jiri Kosina
I stay totally unconvinced that such kind of countermeasure brings any value whatsoever. Could you please bring up a particular usecase, where you have complete control over kernel memory, and still the only possible exploit factor is redirecting usermodhelper? It feels like rather random shot into darkness.
Quoted 4 lines
If we want to make usermod helper really secure, perhaps the best way to go would be to completely nuke it and handle everyhting in udev; that'd be quite some work though, especially so that we don't break all the corner cases of module autoloading (
request_module()and such).
In talking about this with others, I like Neil's approach of just calling out to a statically-defined single binary to handle all of the specifics. Using something like busybox/toybox to handle any usermode helper issues would be a very simple way to deal with this on a large number of systems (i.e. embedded devices / phones / chromebooks).
After I return from vacation, I'll respin this series based on that idea and repost it.
thanks,
greg k-h