Re: [PATCH] mdopen: use safe functions in create_mddev

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 9/21/21 3:52 AM, Mariusz Tkaczyk wrote:
> To avoid buffer overflows, add sizes and use safe functions.
> Buffers are now limited to NAME_MAX. Potentially, it may
> cause regression in non-standard usecases.
> Adapt description to kernel-doc standard.
> Add verification for name and dev to ensure that at least one of them
> is set.
> Remove redundant chosen update after verification. It is set already.
> 
> Signed-off-by: Mariusz Tkaczyk <mariusz.tkaczyk@xxxxxxxxxxxxxxx>
> ---
>  Assemble.c    |   2 +-
>  Build.c       |   2 +-
>  Create.c      |   3 +-
>  Incremental.c |  10 ++--
>  mdadm.h       |   5 +-
>  mdopen.c      | 132 ++++++++++++++++++++++++++++++++------------------
>  6 files changed, 96 insertions(+), 58 deletions(-)

I've been wanting to get back to this one for a while. Sorry it's taken
so long.

The switch to using NAME_MAX has some side effects I am a little
concerned about, however the code is also really tricky to get your head
around (not your fault).

> @@ -160,10 +170,13 @@ int create_named_array(char *devnm)
>   * /dev/mdXX in 'chosen'.
>   *
>   * When we create devices, we use uid/gid/umask from config file.
> + *
> + * Return: O_EXCL file descriptor or negative integer.
> + *
> + * Null terminated name for the volume is returned via *chosen.
>   */
> -
> -int create_mddev(char *dev, char *name, int autof, int trustworthy,
> -		 char *chosen, int block_udev)
> +int create_mddev(const char *dev, const char *name, int autof, int trustworthy,
> +		 char *chosen, const size_t chosen_size, int block_udev)
>  {
>  	int mdfd;
>  	struct stat stb;
> @@ -171,16 +184,24 @@ int create_mddev(char *dev, char *name, int autof, int trustworthy,
>  	int use_mdp = -1;
>  	struct createinfo *ci = conf_get_create_info();
>  	int parts;
> +	const size_t cname_size = NAME_MAX;
>  	char *cname;
> -	char devname[37];
> -	char devnm[32];
> -	char cbuf[400];
> +	char devname[NAME_MAX + 5];
> +	char devnm[NAME_MAX] = "\0";
> +	static const char dev_md_path[] = "/dev/md/";

This is quite a lot of additional stack space used going from 32+37 to
512, however reducing the arbitrary 400 bytes size of cbuf is a good thing.

> @@ -188,35 +209,48 @@ int create_mddev(char *dev, char *name, int autof, int trustworthy,
>  	parts = autof >> 3;
>  	autof &= 7;
>  
> -	strcpy(chosen, "/dev/md/");
> -	cname = chosen + strlen(chosen);
> +	if (chosen_size <= strlen(dev_md_path) + cname_size) {
> +		dprintf("Chosen buffer is to small.\n");
> +		return -1;
> +	}

cname_size = NAME_MAX = 255

Ie. if something calls create_mddev() with a chosen_size smaller than
263, this check will fail, which seems rather arbitrary. It does look
like we always use a chosen_name[1024] which is silly wasteful, but
there much be a better solution to this. Maybe malloc() and return the
buffer instead of relying on those large stack frames?

> +	strncpy(chosen, dev_md_path, chosen_size);
> +	cname = chosen + strlen(dev_md_path);
>  
>  	if (dev) {
> -		if (strncmp(dev, "/dev/md/", 8) == 0) {
> -			strcpy(cname, dev+8);
> -		} else if (strncmp(dev, "/dev/", 5) == 0) {
> -			char *e = dev + strlen(dev);
> +		if (strncmp(dev, dev_md_path, 8) == 0)
> +			strncpy(cname, dev+8, cname_size);

sizeof(dev_md_path) instead of the hardcoded 8?

> +		else if (strncmp(dev, dev_md_path, 5) == 0) {
> +			const char *e = dev + 5 + strnlen(dev + 5, NAME_MAX);
> +>  			while (e > dev && isdigit(e[-1]))
>  				e--;>  			if (e[0])
>  				num = strtoul(e, NULL, 10);
> -			strcpy(cname, dev+5);
> +			strncpy(cname, dev + 5, cname_size);
>  			cname[e-(dev+5)] = 0;
> +
>  			/* name *must* be mdXX or md_dXX in this context */
>  			if (num < 0 ||
> -			    (strcmp(cname, "md") != 0 && strcmp(cname, "md_d") != 0)) {
> +			    (strncmp(cname, "md", 2) != 0 &&
> +			     strncmp(cname, "md_d", 4) != 0)) {
>  				pr_err("%s is an invalid name for an md device.  Try /dev/md/%s\n",
>  					dev, dev+5);
>  				return -1;
>  			}
> -			if (strcmp(cname, "md") == 0)
> +			if (strncmp(cname, "md", 2) == 0)
>  				use_mdp = 0;
>  			else
>  				use_mdp = 1;
>  			/* recreate name: /dev/md/0 or /dev/md/d0 */
> -			sprintf(cname, "%s%d", use_mdp?"d":"", num);
> +			snprintf(cname, cname_size, "%s%d",
> +				 use_mdp ? "d" : "", num);
>  		} else
> -			strcpy(cname, dev);
> +			strncpy(cname, dev, cname_size);
> +		/*
> +		 * Force null termination for long names.
> +		 */
> +		cname[cname_size - 1] = '\0';
>  
>  		/* 'cname' must not contain a slash, and may not be
>  		 * empty.

My head started spinning by the time I got to here.

The more I think about it, the more I think we should allocate an
appropriate buffer in here and return that, rather than play all those
bounds checking games.

Thoughts?

Jes



[Index of Archives]     [Linux RAID Wiki]     [ATA RAID]     [Linux SCSI Target Infrastructure]     [Linux Block]     [Linux IDE]     [Linux SCSI]     [Linux Hams]     [Device Mapper]     [Device Mapper Cryptographics]     [Kernel]     [Linux Admin]     [Linux Net]     [GFS]     [RPM]     [git]     [Yosemite Forum]


  Powered by Linux