Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 21 Jun 2019 19:41:15 +1000 (EST)
From:      Bruce Evans <brde@optusnet.com.au>
To:        Alan Somers <asomers@freebsd.org>
Cc:        src-committers@freebsd.org, svn-src-all@freebsd.org,  svn-src-head@freebsd.org
Subject:   Re: svn commit: r349248 - in head/sys: fs/fifofs kern sys
Message-ID:  <20190621185323.C1023@besplex.bde.org>
In-Reply-To: <201906202307.x5KN7LLB002088@repo.freebsd.org>
References:  <201906202307.x5KN7LLB002088@repo.freebsd.org>

next in thread | previous in thread | raw e-mail | index | archive | help
On Thu, 20 Jun 2019, Alan Somers wrote:

> Log:
>  fcntl: fix overflow when setting F_READAHEAD
>
>  VOP_READ and VOP_WRITE take the seqcount in blocks in a 16-bit field.
>  However, fcntl allows you to set the seqcount in bytes to any nonnegative
>  31-bit value. The result can be a 16-bit overflow, which will be
>  sign-extended in functions like ffs_read. Fix this by sanitizing the
>  argument in kern_fcntl. As a matter of policy, limit to IO_SEQMAX rather
>  than INT16_MAX.
> ...
> Modified: head/sys/kern/vfs_vnops.c
> ==============================================================================
> --- head/sys/kern/vfs_vnops.c	Thu Jun 20 22:21:42 2019	(r349247)
> +++ head/sys/kern/vfs_vnops.c	Thu Jun 20 23:07:20 2019	(r349248)
> @@ -499,7 +499,8 @@ sequential_heuristic(struct uio *uio, struct file *fp)
> 		 * closely related to the best I/O size for real disks than
> 		 * to any block size used by software.
> 		 */
> -		fp->f_seqcount += howmany(uio->uio_resid, 16384);
> +		fp->f_seqcount += MIN(IO_SEQMAX,
> +		    howmany(uio->uio_resid, 16384));
> 		if (fp->f_seqcount > IO_SEQMAX)
> 			fp->f_seqcount = IO_SEQMAX;

This already limited the result to IO_SEQMAX.  Now it limits the result to
IO_SEQMAX twice.

Using MIN() is a style bug.  4.4BSD removed the MIN() macro from the kernel
and replaced it by *min() inline functions.  MIN() was restored because
too much contribed code used it, but it should not be used in core code.

However, *min() is not type-generic, so it is hard to use.  Using

 	f_seqcount = imin(IO_SEQMAX, howmany(uio->uio_resid, 16384));

would restore the overflow bug.  This code worked originally when uio_resid
had type int.  It was broken by changing uio_resid's type to ssize_t.
This makes howmany(uio->uio_resid, 16384) have type ssize_t too, and its
value overflows on assignment to "int f_seqcount" before the value is
clamped to IO_SEQMAX, whenever uio_resid is especially preposterously
large (2G * 16384) on 64-bit systems which pretend to support such silly
i/o sizes.

imin() is as bad as the blind assignment to an int, since its prototype
cause blind conversion to int.  I have some macros for *min() which
optionally detect such type errors or just do much the same as MIN()
(but using inline functions), but these are not quite of production
quality.  The case where one arg has a signed type and the other arg
has a signed type is hardest to handle.  This has sign extension or
unsign extension bugs in general.  MIN() blindly does the extension.


The overflow usually only causes negative f_seqcount which should be
treated as 0.

> 		return (fp->f_seqcount << IO_SEQSHIFT);
>
> Modified: head/sys/sys/file.h
> ==============================================================================
> --- head/sys/sys/file.h	Thu Jun 20 22:21:42 2019	(r349247)
> +++ head/sys/sys/file.h	Thu Jun 20 23:07:20 2019	(r349248)
> @@ -179,7 +179,10 @@ struct file {
> 	/*
> 	 *  DTYPE_VNODE specific fields.
> 	 */
> -	int		f_seqcount;	/* (a) Count of sequential accesses. */
> +	union {
> +		int16_t	f_seqcount;	/* (a) Count of sequential accesses. */
> +		int	f_pipegen;
> +	};
> 	off_t		f_nextoff;	/* next expected read/write offset. */
> 	union {
> 		struct cdev_privdata *fvn_cdevpriv;

f_seqcount should still have type int.  Conversion of any type smaller than
int to int just wastes time, and int16_t is a bogus type for holding values
limited to 0x7F.

int for f_seqcount costs no space, since f_pipegen still has the correct
type and the union is padded to at least the size of its largest member.

This struct has delicate ordering which usually minimizes padding,
although it violates style(9) for most integer members.  Before
f_seqcount, there are some pointers, then 2 shorts and 2 u_int's, so
64-bit alignment occurs after f_seqcount on 64-bit arches, just in
time for there to be no padding before the 64-bit f_offset.  style(9)
requires sorting all the 64-bit types first.

My macros look like:

XX #define __min(x, y)							\
XX (									\
XX 	(sizeof(x) == 8 || sizeof(y) == 8) ?				\
XX 		((__typeof(x))-1 == -1 && (__typeof(y))-1 == -1) ?	\
XX 			_qmin((x), (y))					\
XX 		:							\
XX 			_uqmin((x), (y))				\
XX 	:								\
XX 		((__typeof(x))-1 == -1 && (__typeof(y))-1 == -1) ?	\
XX 			_imin((x), (y))					\
XX 		:							\
XX 			_min((x), (y))					\
XX )
XX 
XX #ifdef MACRO_MIN
XX static __inline int _imin(int a, int b) { return (a < b ? a : b); }
XX ...
XX #define	imin(a, b)	__min((a), (b))
XX #else
XX #ifdef __GNUC__
XX static __inline int imin(int a, int b) { return (a < b ? a : b); }
XX #endif
XX #endif

One reason that these are not production quality is that they require
gnu __typeof() but production quality must work with any C compiler.
This worked with gcc-1 or 2.  Later versions of gcc have better support
for type-generic functions, but that is even more unportable.

These can be used to find type errors: compile without and with MACRO_MIN.
If the object code changes, then the non-underscored function must have
had wrong types since the type-generic macro selected a sub-version with
different types.

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20190621185323.C1023>