diff options
Diffstat (limited to 'drivers/md/dm-ioctl.c')
| -rw-r--r-- | drivers/md/dm-ioctl.c | 98 | 
1 files changed, 75 insertions, 23 deletions
diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index 6d301019e5e3..f5ed729a8e0c 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -767,7 +767,14 @@ static int get_target_version(struct file *filp, struct dm_ioctl *param, size_t  static int check_name(const char *name)  {  	if (strchr(name, '/')) { -		DMERR("invalid device name"); +		DMERR("device name cannot contain '/'"); +		return -EINVAL; +	} + +	if (strcmp(name, DM_CONTROL_NODE) == 0 || +	    strcmp(name, ".") == 0 || +	    strcmp(name, "..") == 0) { +		DMERR("device name cannot be \"%s\", \".\", or \"..\"", DM_CONTROL_NODE);  		return -EINVAL;  	} @@ -1388,16 +1395,38 @@ static inline blk_mode_t get_mode(struct dm_ioctl *param)  	return mode;  } -static int next_target(struct dm_target_spec *last, uint32_t next, void *end, +static int next_target(struct dm_target_spec *last, uint32_t next, const char *end,  		       struct dm_target_spec **spec, char **target_params)  { -	*spec = (struct dm_target_spec *) ((unsigned char *) last + next); -	*target_params = (char *) (*spec + 1); +	static_assert(__alignof__(struct dm_target_spec) <= 8, +		"struct dm_target_spec must not require more than 8-byte alignment"); + +	/* +	 * Number of bytes remaining, starting with last. This is always +	 * sizeof(struct dm_target_spec) or more, as otherwise *last was +	 * out of bounds already. +	 */ +	size_t remaining = end - (char *)last; + +	/* +	 * There must be room for both the next target spec and the +	 * NUL-terminator of the target itself. +	 */ +	if (remaining - sizeof(struct dm_target_spec) <= next) { +		DMERR("Target spec extends beyond end of parameters"); +		return -EINVAL; +	} -	if (*spec < (last + 1)) +	if (next % __alignof__(struct dm_target_spec)) { +		DMERR("Next dm_target_spec (offset %u) is not %zu-byte aligned", +		      next, __alignof__(struct dm_target_spec));  		return -EINVAL; +	} + +	*spec = (struct dm_target_spec *) ((unsigned char *) last + next); +	*target_params = (char *) (*spec + 1); -	return invalid_str(*target_params, end); +	return 0;  }  static int populate_table(struct dm_table *table, @@ -1407,8 +1436,9 @@ static int populate_table(struct dm_table *table,  	unsigned int i = 0;  	struct dm_target_spec *spec = (struct dm_target_spec *) param;  	uint32_t next = param->data_start; -	void *end = (void *) param + param_size; +	const char *const end = (const char *) param + param_size;  	char *target_params; +	size_t min_size = sizeof(struct dm_ioctl);  	if (!param->target_count) {  		DMERR("%s: no targets specified", __func__); @@ -1416,6 +1446,13 @@ static int populate_table(struct dm_table *table,  	}  	for (i = 0; i < param->target_count; i++) { +		const char *nul_terminator; + +		if (next < min_size) { +			DMERR("%s: next target spec (offset %u) overlaps %s", +			      __func__, next, i ? "previous target" : "'struct dm_ioctl'"); +			return -EINVAL; +		}  		r = next_target(spec, next, end, &spec, &target_params);  		if (r) { @@ -1423,6 +1460,15 @@ static int populate_table(struct dm_table *table,  			return r;  		} +		nul_terminator = memchr(target_params, 0, (size_t)(end - target_params)); +		if (nul_terminator == NULL) { +			DMERR("%s: target parameters not NUL-terminated", __func__); +			return -EINVAL; +		} + +		/* Add 1 for NUL terminator */ +		min_size = (size_t)(nul_terminator - (const char *)spec) + 1; +  		r = dm_table_add_target(table, spec->target_type,  					(sector_t) spec->sector_start,  					(sector_t) spec->length, @@ -1830,30 +1876,36 @@ static ioctl_fn lookup_ioctl(unsigned int cmd, int *ioctl_flags)   * As well as checking the version compatibility this always   * copies the kernel interface version out.   */ -static int check_version(unsigned int cmd, struct dm_ioctl __user *user) +static int check_version(unsigned int cmd, struct dm_ioctl __user *user, +			 struct dm_ioctl *kernel_params)  { -	uint32_t version[3];  	int r = 0; -	if (copy_from_user(version, user->version, sizeof(version))) +	/* Make certain version is first member of dm_ioctl struct */ +	BUILD_BUG_ON(offsetof(struct dm_ioctl, version) != 0); + +	if (copy_from_user(kernel_params->version, user->version, sizeof(kernel_params->version)))  		return -EFAULT; -	if ((version[0] != DM_VERSION_MAJOR) || -	    (version[1] > DM_VERSION_MINOR)) { +	if ((kernel_params->version[0] != DM_VERSION_MAJOR) || +	    (kernel_params->version[1] > DM_VERSION_MINOR)) {  		DMERR("ioctl interface mismatch: kernel(%u.%u.%u), user(%u.%u.%u), cmd(%d)",  		      DM_VERSION_MAJOR, DM_VERSION_MINOR,  		      DM_VERSION_PATCHLEVEL, -		      version[0], version[1], version[2], cmd); +		      kernel_params->version[0], +		      kernel_params->version[1], +		      kernel_params->version[2], +		      cmd);  		r = -EINVAL;  	}  	/*  	 * Fill in the kernel version.  	 */ -	version[0] = DM_VERSION_MAJOR; -	version[1] = DM_VERSION_MINOR; -	version[2] = DM_VERSION_PATCHLEVEL; -	if (copy_to_user(user->version, version, sizeof(version))) +	kernel_params->version[0] = DM_VERSION_MAJOR; +	kernel_params->version[1] = DM_VERSION_MINOR; +	kernel_params->version[2] = DM_VERSION_PATCHLEVEL; +	if (copy_to_user(user->version, kernel_params->version, sizeof(kernel_params->version)))  		return -EFAULT;  	return r; @@ -1877,9 +1929,11 @@ static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl *param_kern  	struct dm_ioctl *dmi;  	int secure_data;  	const size_t minimum_data_size = offsetof(struct dm_ioctl, data); -	unsigned int noio_flag; -	if (copy_from_user(param_kernel, user, minimum_data_size)) +	/* check_version() already copied version from userspace, avoid TOCTOU */ +	if (copy_from_user((char *)param_kernel + sizeof(param_kernel->version), +			   (char __user *)user + sizeof(param_kernel->version), +			   minimum_data_size - sizeof(param_kernel->version)))  		return -EFAULT;  	if (param_kernel->data_size < minimum_data_size) { @@ -1904,9 +1958,7 @@ static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl *param_kern  	 * Use kmalloc() rather than vmalloc() when we can.  	 */  	dmi = NULL; -	noio_flag = memalloc_noio_save(); -	dmi = kvmalloc(param_kernel->data_size, GFP_KERNEL | __GFP_HIGH); -	memalloc_noio_restore(noio_flag); +	dmi = kvmalloc(param_kernel->data_size, GFP_NOIO | __GFP_HIGH);  	if (!dmi) {  		if (secure_data && clear_user(user, param_kernel->data_size)) @@ -1991,7 +2043,7 @@ static int ctl_ioctl(struct file *file, uint command, struct dm_ioctl __user *us  	 * Check the interface version passed in.  This also  	 * writes out the kernel's interface version.  	 */ -	r = check_version(cmd, user); +	r = check_version(cmd, user, ¶m_kernel);  	if (r)  		return r;  |