diff mbox series

[v1,1/2] gpiolib: Fix line event handling in syscall compatible mode

Message ID 20200910101935.47140-1-andriy.shevchenko@linux.intel.com
State New
Headers show
Series [v1,1/2] gpiolib: Fix line event handling in syscall compatible mode | expand

Commit Message

Andy Shevchenko Sept. 10, 2020, 10:19 a.m. UTC
The introduced line even handling ABI in the commit

  61f922db7221 ("gpio: userspace ABI for reading GPIO line events")

missed the fact that 64-bit kernel may serve for 32-bit applications.
In such case the very first check in the lineevent_read() will fail
due to alignment differences.

To workaround this introduce lineeven_to_user() helper which returns actual
size of the structure and copies its content to user if asked.

Fixes: 61f922db7221 ("gpio: userspace ABI for reading GPIO line events")
Suggested-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
 drivers/gpio/gpiolib-cdev.c | 41 ++++++++++++++++++++++++++++++++-----
 1 file changed, 36 insertions(+), 5 deletions(-)

Comments

kernel test robot Sept. 11, 2020, 1:37 a.m. UTC | #1
Hi Andy,

I love your patch! Perhaps something to improve:

[auto build test WARNING on gpio/for-next]
[also build test WARNING on v5.9-rc4 next-20200910]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Andy-Shevchenko/gpiolib-Fix-line-event-handling-in-syscall-compatible-mode/20200910-182240
base:   https://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio.git for-next
config: ia64-randconfig-s032-20200909 (attached as .config)
compiler: ia64-linux-gcc (GCC) 9.3.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # apt-get install sparse
        # sparse version: v0.6.2-191-g10164920-dirty
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' ARCH=ia64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)

>> drivers/gpio/gpiolib-cdev.c:432:41: sparse: sparse: expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:432:41: sparse: sparse: Expected } at end of struct-union-enum-specifier
   drivers/gpio/gpiolib-cdev.c:432:41: sparse: sparse: got timestamp
   drivers/gpio/gpiolib-cdev.c:446:17: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:446:17: sparse: sparse: got &&
>> drivers/gpio/gpiolib-cdev.c:446:9: sparse: sparse: Trying to use reserved word 'if' as identifier
   drivers/gpio/gpiolib-cdev.c:449:9: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:449:9: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:452:1: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:452:1: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:467:19: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:467:19: sparse: sparse: got <
   drivers/gpio/gpiolib-cdev.c:467:9: sparse: sparse: Trying to use reserved word 'if' as identifier
>> drivers/gpio/gpiolib-cdev.c:470:9: sparse: sparse: Trying to use reserved word 'do' as identifier
   drivers/gpio/gpiolib-cdev.c:470:12: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:470:12: sparse: sparse: got {
   drivers/gpio/gpiolib-cdev.c:472:21: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:472:21: sparse: sparse: got (
   drivers/gpio/gpiolib-cdev.c:472:17: sparse: sparse: Trying to use reserved word 'if' as identifier
   drivers/gpio/gpiolib-cdev.c:472:21: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:472:21: sparse: sparse: got ->
   drivers/gpio/gpiolib-cdev.c:472:21: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:472:21: sparse: sparse: got }
>> drivers/gpio/gpiolib-cdev.c:475:33: sparse: sparse: Trying to use reserved word 'return' as identifier
   drivers/gpio/gpiolib-cdev.c:475:40: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:475:40: sparse: sparse: got bytes_read
   drivers/gpio/gpiolib-cdev.c:476:25: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:476:25: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:480:33: sparse: sparse: Trying to use reserved word 'return' as identifier
   drivers/gpio/gpiolib-cdev.c:480:40: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:480:40: sparse: sparse: got -
   drivers/gpio/gpiolib-cdev.c:481:25: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:481:25: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got ->
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got 0
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'if' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'do' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got {
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'if' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got break
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got ->
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got &
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'do' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got {
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got (
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'if' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ) in nested declarator
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got {
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got (
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'if' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got "drivers/gpio/gpiolib-cdev.c"
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'do' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got {
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got !
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at the end of type declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got }
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ) in nested declarator
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got task_struct
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'struct' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ) in function declarator
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got 1037
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'switch' as identifier
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'break' as identifier
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'case' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got 1016
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'break' as identifier
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'case' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got 1019
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'break' as identifier
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'case' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: got 1037
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'break' as identifier
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Trying to use reserved word 'case' as identifier
   drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: Expected ; at end of declaration
>> drivers/gpio/gpiolib-cdev.c:483:31: sparse: sparse: too many errors

# https://github.com/0day-ci/linux/commit/402a73d41d7b2b3a72dc346e62d2b4c4403a6277
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Andy-Shevchenko/gpiolib-Fix-line-event-handling-in-syscall-compatible-mode/20200910-182240
git checkout 402a73d41d7b2b3a72dc346e62d2b4c4403a6277
vim +432 drivers/gpio/gpiolib-cdev.c

   425	
   426	static ssize_t lineevent_to_user(char __user *buf, struct gpioevent_data *ge)
   427	{
   428	#ifdef __x86_64__
   429		/* i386 has no padding after 'id' */
   430		if (in_ia32_syscall()) {
   431			struct compat_ge {
 > 432				compat_u64	timestamp __packed;
   433				u32		id;
   434			} cge;
   435	
   436			if (buf && ge) {
   437				cge = (struct compat_ge){ ge->timestamp, ge->id };
   438				if (copy_to_user(buf, &cge, sizeof(cge)))
   439					return -EFAULT;
   440			}
   441	
   442			return sizeof(cge);
   443		}
   444	#endif
   445	
 > 446		if (buf && ge) {
   447			if (copy_to_user(buf, ge, sizeof(*ge)))
   448				return -EFAULT;
   449		}
   450	
   451		return sizeof(*ge);
   452	}
   453	
   454	static ssize_t lineevent_read(struct file *file,
   455				      char __user *buf,
   456				      size_t count,
   457				      loff_t *f_ps)
   458	{
   459		struct lineevent_state *le = file->private_data;
   460		struct gpioevent_data ge;
   461		ssize_t bytes_read = 0;
   462		ssize_t ge_size;
   463		int ret;
   464	
   465		/* When argument is NULL it returns size of the structure in user space */
   466		ge_size = lineevent_to_user(NULL, NULL);
   467		if (count < ge_size)
   468			return -EINVAL;
   469	
 > 470		do {
   471			spin_lock(&le->wait.lock);
   472			if (kfifo_is_empty(&le->events)) {
   473				if (bytes_read) {
   474					spin_unlock(&le->wait.lock);
 > 475					return bytes_read;
   476				}
   477	
   478				if (file->f_flags & O_NONBLOCK) {
   479					spin_unlock(&le->wait.lock);
   480					return -EAGAIN;
   481				}
   482	
 > 483				ret = wait_event_interruptible_locked(le->wait,
   484						!kfifo_is_empty(&le->events));
   485				if (ret) {
   486					spin_unlock(&le->wait.lock);
   487					return ret;
   488				}
   489			}
   490	
   491			ret = kfifo_out(&le->events, &ge, 1);
   492			spin_unlock(&le->wait.lock);
   493			if (ret != 1) {
   494				/*
   495				 * This should never happen - we were holding the lock
   496				 * from the moment we learned the fifo is no longer
   497				 * empty until now.
   498				 */
   499				ret = -EIO;
   500				break;
   501			}
   502	
   503			ret = lineevent_to_user(buf + bytes_read, &ge);
   504			if (ret < 0)
   505				return ret;
   506			bytes_read += ret;
   507		} while (count >= bytes_read + ge_size);
   508	
   509		return bytes_read;
   510	}
   511	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Kent Gibson Sept. 11, 2020, 3:05 a.m. UTC | #2
On Thu, Sep 10, 2020 at 01:19:34PM +0300, Andy Shevchenko wrote:
> The introduced line even handling ABI in the commit
> 

s/even/event/

>   61f922db7221 ("gpio: userspace ABI for reading GPIO line events")
> 
> missed the fact that 64-bit kernel may serve for 32-bit applications.
> In such case the very first check in the lineevent_read() will fail
> due to alignment differences.
> 
> To workaround this introduce lineeven_to_user() helper which returns actual
> size of the structure and copies its content to user if asked.
> 

And again here.

> Fixes: 61f922db7221 ("gpio: userspace ABI for reading GPIO line events")
> Suggested-by: Arnd Bergmann <arnd@arndb.de>
> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> ---
>  drivers/gpio/gpiolib-cdev.c | 41 ++++++++++++++++++++++++++++++++-----
>  1 file changed, 36 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
> index e6c9b78adfc2..a6a8384c8255 100644
> --- a/drivers/gpio/gpiolib-cdev.c
> +++ b/drivers/gpio/gpiolib-cdev.c
> @@ -423,6 +423,33 @@ static __poll_t lineevent_poll(struct file *file,
>  	return events;
>  }
>  
> +static ssize_t lineevent_to_user(char __user *buf, struct gpioevent_data *ge)
> +{
> +#ifdef __x86_64__
> +	/* i386 has no padding after 'id' */
> +	if (in_ia32_syscall()) {
> +		struct compat_ge {
> +			compat_u64	timestamp __packed;
> +			u32		id;
> +		} cge;
> +
> +		if (buf && ge) {
> +			cge = (struct compat_ge){ ge->timestamp, ge->id };
> +			if (copy_to_user(buf, &cge, sizeof(cge)))
> +				return -EFAULT;
> +		}
> +
> +		return sizeof(cge);
> +	}
> +#endif
> +
> +	if (buf && ge) {
> +		if (copy_to_user(buf, ge, sizeof(*ge)))
> +			return -EFAULT;
> +	}
> +
> +	return sizeof(*ge);
> +}
>  

The dual-purpose nature of this function makes it more complicated than
it needs to be.
I was going to suggest splitting it into separate functions, but...

Given that struct compat_ge is a strict truncation of struct
gpioevent_data, how about reducing this function to just determining the
size of the event for user space, say lineevent_user_size(), and
replacing sizeof(ge) with that calculcated size throughout
lineevent_read()?

>  static ssize_t lineevent_read(struct file *file,
>  			      char __user *buf,
> @@ -432,9 +459,12 @@ static ssize_t lineevent_read(struct file *file,
>  	struct lineevent_state *le = file->private_data;
>  	struct gpioevent_data ge;
>  	ssize_t bytes_read = 0;
> +	ssize_t ge_size;
>  	int ret;
>  
> -	if (count < sizeof(ge))
> +	/* When argument is NULL it returns size of the structure in user space */
> +	ge_size = lineevent_to_user(NULL, NULL);
> +	if (count < ge_size)
>  		return -EINVAL;
>  
>  	do {
> @@ -470,10 +500,11 @@ static ssize_t lineevent_read(struct file *file,
>  			break;
>  		}
>  
> -		if (copy_to_user(buf + bytes_read, &ge, sizeof(ge)))
> -			return -EFAULT;
> -		bytes_read += sizeof(ge);
> -	} while (count >= bytes_read + sizeof(ge));
> +		ret = lineevent_to_user(buf + bytes_read, &ge);
> +		if (ret < 0)
> +			return ret;
> +		bytes_read += ret;
> +	} while (count >= bytes_read + ge_size);
>  
>  	return bytes_read;
>  }
> -- 
> 2.28.0
> 

Is patch 2 in any way related to this patch?

Cheers,
Kent.
Andy Shevchenko Sept. 11, 2020, 8:31 a.m. UTC | #3
On Fri, Sep 11, 2020 at 11:05:39AM +0800, Kent Gibson wrote:
> On Thu, Sep 10, 2020 at 01:19:34PM +0300, Andy Shevchenko wrote:
> > The introduced line even handling ABI in the commit
> > 
> 
> s/even/event/
> 
> >   61f922db7221 ("gpio: userspace ABI for reading GPIO line events")
> > 
> > missed the fact that 64-bit kernel may serve for 32-bit applications.
> > In such case the very first check in the lineevent_read() will fail
> > due to alignment differences.
> > 
> > To workaround this introduce lineeven_to_user() helper which returns actual
> > size of the structure and copies its content to user if asked.
> > 
> 
> And again here.

Thanks!

...

> > +#ifdef __x86_64__
> > +	/* i386 has no padding after 'id' */
> > +	if (in_ia32_syscall()) {
> > +		struct compat_ge {
> > +			compat_u64	timestamp __packed;
> > +			u32		id;
> > +		} cge;
> > +
> > +		if (buf && ge) {
> > +			cge = (struct compat_ge){ ge->timestamp, ge->id };
> > +			if (copy_to_user(buf, &cge, sizeof(cge)))
> > +				return -EFAULT;
> > +		}
> > +
> > +		return sizeof(cge);
> > +	}
> > +#endif
> > +
> > +	if (buf && ge) {
> > +		if (copy_to_user(buf, ge, sizeof(*ge)))
> > +			return -EFAULT;
> > +	}
> > +
> > +	return sizeof(*ge);
> > +}
> >  
> 
> The dual-purpose nature of this function makes it more complicated than
> it needs to be.
> I was going to suggest splitting it into separate functions, but...
> 
> Given that struct compat_ge is a strict truncation of struct
> gpioevent_data, how about reducing this function to just determining the
> size of the event for user space, say lineevent_user_size(), and
> replacing sizeof(ge) with that calculcated size throughout
> lineevent_read()?

So you mean something like

	struct compat_gpioeevent_data {
		compat_u64	timestamp;
		u32		id;
	} __packed;

#ifdef __x86_64__
	/* i386 has no padding after 'id' */
	size_t ge_size = in_ia32_syscall() ? sizeof(struct compat_gpioevent_data) : sizeof(struct gpioevent_data);
#else
	size_t ge_size = sizeof(struct gpioevent_data) ;
#endif

?

...

> Is patch 2 in any way related to this patch?

No. It can be applied separately.
Kent Gibson Sept. 11, 2020, 9:12 a.m. UTC | #4
On Fri, Sep 11, 2020 at 11:31:09AM +0300, Andy Shevchenko wrote:
> On Fri, Sep 11, 2020 at 11:05:39AM +0800, Kent Gibson wrote:
> > On Thu, Sep 10, 2020 at 01:19:34PM +0300, Andy Shevchenko wrote:
> > > The introduced line even handling ABI in the commit
> > > 
> > 
> > s/even/event/
> > 
> > >   61f922db7221 ("gpio: userspace ABI for reading GPIO line events")
> > > 
> > > missed the fact that 64-bit kernel may serve for 32-bit applications.
> > > In such case the very first check in the lineevent_read() will fail
> > > due to alignment differences.
> > > 
> > > To workaround this introduce lineeven_to_user() helper which returns actual
> > > size of the structure and copies its content to user if asked.
> > > 
> > 
> > And again here.
> 
> Thanks!
>
> ...
> 
> > > +#ifdef __x86_64__
> > > +	/* i386 has no padding after 'id' */
> > > +	if (in_ia32_syscall()) {
> > > +		struct compat_ge {
> > > +			compat_u64	timestamp __packed;
> > > +			u32		id;
> > > +		} cge;
> > > +
> > > +		if (buf && ge) {
> > > +			cge = (struct compat_ge){ ge->timestamp, ge->id };
> > > +			if (copy_to_user(buf, &cge, sizeof(cge)))
> > > +				return -EFAULT;
> > > +		}
> > > +
> > > +		return sizeof(cge);
> > > +	}
> > > +#endif
> > > +
> > > +	if (buf && ge) {
> > > +		if (copy_to_user(buf, ge, sizeof(*ge)))
> > > +			return -EFAULT;
> > > +	}
> > > +
> > > +	return sizeof(*ge);
> > > +}
> > >  
> > 
> > The dual-purpose nature of this function makes it more complicated than
> > it needs to be.
> > I was going to suggest splitting it into separate functions, but...
> > 
> > Given that struct compat_ge is a strict truncation of struct
> > gpioevent_data, how about reducing this function to just determining the
> > size of the event for user space, say lineevent_user_size(), and
> > replacing sizeof(ge) with that calculcated size throughout
> > lineevent_read()?
> 
> So you mean something like
> 
> 	struct compat_gpioeevent_data {
> 		compat_u64	timestamp;
> 		u32		id;
> 	} __packed;
> 
> #ifdef __x86_64__
> 	/* i386 has no padding after 'id' */
> 	size_t ge_size = in_ia32_syscall() ? sizeof(struct compat_gpioevent_data) : sizeof(struct gpioevent_data);
> #else
> 	size_t ge_size = sizeof(struct gpioevent_data) ;
> #endif
> 
> ?
> 

Pretty much, though I was suggesting keeping it in a helper function,
say lineevent_user_size(), not in lineevent_read() itself, to isolate
all the ugliness in one small place.

So in lineevent_read() you would:

   size_t ge_size = lineevent_user_size();

and then use that to replace all the sizeof(ge) occurrences.

Cheers,
Kent.
Andy Shevchenko Sept. 11, 2020, 9:53 a.m. UTC | #5
On Fri, Sep 11, 2020 at 05:12:49PM +0800, Kent Gibson wrote:
> On Fri, Sep 11, 2020 at 11:31:09AM +0300, Andy Shevchenko wrote:
> > On Fri, Sep 11, 2020 at 11:05:39AM +0800, Kent Gibson wrote:
> > > On Thu, Sep 10, 2020 at 01:19:34PM +0300, Andy Shevchenko wrote:

...

> > > > +#ifdef __x86_64__
> > > > +	/* i386 has no padding after 'id' */
> > > > +	if (in_ia32_syscall()) {
> > > > +		struct compat_ge {
> > > > +			compat_u64	timestamp __packed;
> > > > +			u32		id;
> > > > +		} cge;
> > > > +
> > > > +		if (buf && ge) {
> > > > +			cge = (struct compat_ge){ ge->timestamp, ge->id };
> > > > +			if (copy_to_user(buf, &cge, sizeof(cge)))
> > > > +				return -EFAULT;
> > > > +		}
> > > > +
> > > > +		return sizeof(cge);
> > > > +	}
> > > > +#endif
> > > > +
> > > > +	if (buf && ge) {
> > > > +		if (copy_to_user(buf, ge, sizeof(*ge)))
> > > > +			return -EFAULT;
> > > > +	}
> > > > +
> > > > +	return sizeof(*ge);
> > > > +}
> > > 
> > > The dual-purpose nature of this function makes it more complicated than
> > > it needs to be.
> > > I was going to suggest splitting it into separate functions, but...
> > > 
> > > Given that struct compat_ge is a strict truncation of struct
> > > gpioevent_data, how about reducing this function to just determining the
> > > size of the event for user space, say lineevent_user_size(), and
> > > replacing sizeof(ge) with that calculcated size throughout
> > > lineevent_read()?
> > 
> > So you mean something like
> > 
> > 	struct compat_gpioeevent_data {
> > 		compat_u64	timestamp;
> > 		u32		id;
> > 	} __packed;
> > 
> > #ifdef __x86_64__
> > 	/* i386 has no padding after 'id' */
> > 	size_t ge_size = in_ia32_syscall() ? sizeof(struct compat_gpioevent_data) : sizeof(struct gpioevent_data);
> > #else
> > 	size_t ge_size = sizeof(struct gpioevent_data) ;
> > #endif
> > 
> > ?
> > 
> 
> Pretty much, though I was suggesting keeping it in a helper function,
> say lineevent_user_size(), not in lineevent_read() itself, to isolate
> all the ugliness in one small place.
> 
> So in lineevent_read() you would:
> 
>    size_t ge_size = lineevent_user_size();
> 
> and then use that to replace all the sizeof(ge) occurrences.

But in any case it makes code a bit hackish, no?

We calculate the size of one structure and by the fact *partially* copy
another one.

And actually if you look closer to the types, the compat_u64 is not the same as u64.
So, I'm not sure your solution would work in all cases.
Kent Gibson Sept. 11, 2020, 10:17 a.m. UTC | #6
On Fri, Sep 11, 2020 at 12:53:55PM +0300, Andy Shevchenko wrote:
> On Fri, Sep 11, 2020 at 05:12:49PM +0800, Kent Gibson wrote:
> > On Fri, Sep 11, 2020 at 11:31:09AM +0300, Andy Shevchenko wrote:
> > > On Fri, Sep 11, 2020 at 11:05:39AM +0800, Kent Gibson wrote:
> > > > On Thu, Sep 10, 2020 at 01:19:34PM +0300, Andy Shevchenko wrote:
> 
> ...
> 
> > > > > +#ifdef __x86_64__
> > > > > +	/* i386 has no padding after 'id' */
> > > > > +	if (in_ia32_syscall()) {
> > > > > +		struct compat_ge {
> > > > > +			compat_u64	timestamp __packed;
> > > > > +			u32		id;
> > > > > +		} cge;
> > > > > +
> > > > > +		if (buf && ge) {
> > > > > +			cge = (struct compat_ge){ ge->timestamp, ge->id };
> > > > > +			if (copy_to_user(buf, &cge, sizeof(cge)))
> > > > > +				return -EFAULT;
> > > > > +		}
> > > > > +
> > > > > +		return sizeof(cge);
> > > > > +	}
> > > > > +#endif
> > > > > +
> > > > > +	if (buf && ge) {
> > > > > +		if (copy_to_user(buf, ge, sizeof(*ge)))
> > > > > +			return -EFAULT;
> > > > > +	}
> > > > > +
> > > > > +	return sizeof(*ge);
> > > > > +}
> > > > 
> > > > The dual-purpose nature of this function makes it more complicated than
> > > > it needs to be.
> > > > I was going to suggest splitting it into separate functions, but...
> > > > 
> > > > Given that struct compat_ge is a strict truncation of struct
> > > > gpioevent_data, how about reducing this function to just determining the
> > > > size of the event for user space, say lineevent_user_size(), and
> > > > replacing sizeof(ge) with that calculcated size throughout
> > > > lineevent_read()?
> > > 
> > > So you mean something like
> > > 
> > > 	struct compat_gpioeevent_data {
> > > 		compat_u64	timestamp;
> > > 		u32		id;
> > > 	} __packed;
> > > 
> > > #ifdef __x86_64__
> > > 	/* i386 has no padding after 'id' */
> > > 	size_t ge_size = in_ia32_syscall() ? sizeof(struct compat_gpioevent_data) : sizeof(struct gpioevent_data);
> > > #else
> > > 	size_t ge_size = sizeof(struct gpioevent_data) ;
> > > #endif
> > > 
> > > ?
> > > 
> > 
> > Pretty much, though I was suggesting keeping it in a helper function,
> > say lineevent_user_size(), not in lineevent_read() itself, to isolate
> > all the ugliness in one small place.
> > 
> > So in lineevent_read() you would:
> > 
> >    size_t ge_size = lineevent_user_size();
> > 
> > and then use that to replace all the sizeof(ge) occurrences.
> 
> But in any case it makes code a bit hackish, no?
> 

Maybe - I'm not totally happy about copying a partial struct either, but
one way or the other that is what you effectively need to do.

> We calculate the size of one structure and by the fact *partially* copy
> another one.
> 

Perhaps it is a matter of perspective - as I see it you are calculating
the length of the event that the userspace expects, and then copying only
that amount.
As you say in the comment "i386 has no padding after 'id'" - other than
that it is bitwise identical - the only difference is the length.

> And actually if you look closer to the types, the compat_u64 is not the same as u64.
> So, I'm not sure your solution would work in all cases.
> 

There is only one corner case here - 32-bit on x86_64, and it works for
that - I've tested it.
For all other cases it falls back to the full length.

For x86 the compat_u64 is:

typedef u64 __attribute__((aligned(4))) compat_u64;

which is bitwise identical - only allowed to 32-bit align.
And if compat_u64 wasn't bitwise identical in this case then your original
code wouldn't work either ;-).

Cheers,
Kent.
Arnd Bergmann Sept. 11, 2020, 4:20 p.m. UTC | #7
> +static ssize_t lineevent_to_user(char __user *buf, struct gpioevent_data *ge)
> +{
> +#ifdef __x86_64__

I would make this "#ifdef CONFIG_IA32_COMPAT" to clarify what this
is actually checking for. In theory we could add support for
CONFIG_OABI_COMPAT here as well, not sure if there is a point.
I recently came across a couple of things that all need the same
hacks for arm-oabi and x86-32 in principle.

> +       /* i386 has no padding after 'id' */
> +       if (in_ia32_syscall()) {
> +               struct compat_ge {
> +                       compat_u64      timestamp __packed;

No need for marking this __packed, it already is.

> +                       u32             id;
> +               } cge;
> +
> +               if (buf && ge) {

I think I'd leave out the 'if()' checks here, and require the function
to be called with valid pointers. It seems odd otherwise to return
sizeof(cge) from the read() function without having written data.

Note also that user space may pass a NULL pointer and should
get back -EFAULT instead of 12 or 16.

> -       if (count < sizeof(ge))
> +       /* When argument is NULL it returns size of the structure in user space */
> +       ge_size = lineevent_to_user(NULL, NULL);
> +       if (count < ge_size)
>                 return -EINVAL;

Right, I see this is how it's being used, and I'd tend to agree with Kent:
if you just determine the size dynamically and add a good comment,
then the rest of the code gets simpler and more logical.

      Arnd
Kent Gibson Sept. 12, 2020, 2:21 a.m. UTC | #8
On Fri, Sep 11, 2020 at 05:28:46PM +0300, Andy Shevchenko wrote:
> On Fri, Sep 11, 2020 at 06:17:14PM +0800, Kent Gibson wrote:
> > On Fri, Sep 11, 2020 at 12:53:55PM +0300, Andy Shevchenko wrote:
> > > On Fri, Sep 11, 2020 at 05:12:49PM +0800, Kent Gibson wrote:
> > > > On Fri, Sep 11, 2020 at 11:31:09AM +0300, Andy Shevchenko wrote:
> > > > > On Fri, Sep 11, 2020 at 11:05:39AM +0800, Kent Gibson wrote:
> > > > > > On Thu, Sep 10, 2020 at 01:19:34PM +0300, Andy Shevchenko wrote:
> > > 

[snip]
> > 
> > typedef u64 __attribute__((aligned(4))) compat_u64;
> > 
> > which is bitwise identical - only allowed to 32-bit align.
> 
> Yes. That's what I meant under "not the same".
> 
> As far as I understand the alignment makes sense if this type is a part of
> the uAPI definition. But here we have it completely local. copy_to_user() takes
> a pointer to a memory without any specific alignment implied.
> 
> So, what you proposing is basically something like
> 
> ret = copy_to_user(buf, &ge, compat ?  sizeof(compat) : sizeof(ge));
> 
> Correct?
> 

That isn't how I would write the copy_to_user(). The size would be
calculated once, using the linevent_user_size() helper, with
appropriate documentation as to why this is necessary, and then
used throughout lineevent_read().

The documentation would mainly be on the lineevent_user_size() function
itself.

> I don't like the difference between 2nd and 3rd argument. This what looks to me
> hackish. Variant with explicit compat structure I like more.
> 

Agreed - writing it that way does look pretty nasty.

But my suggestion is actually this:

ret = copy_to_user(buf, &ge, event_size);

I suggested ge_size previously, but event_size might help highlight that
it isn't always sizeof(ge).

> But if you think it's okay, I will update your way.
> 

I would defer to Bart or Linus, but I think just calculating the
appropriate size is preferable for this case.

Cheers,
Kent.
Bartosz Golaszewski Sept. 14, 2020, 12:44 p.m. UTC | #9
On Sat, Sep 12, 2020 at 4:21 AM Kent Gibson <warthog618@gmail.com> wrote:
>
> On Fri, Sep 11, 2020 at 05:28:46PM +0300, Andy Shevchenko wrote:
> > On Fri, Sep 11, 2020 at 06:17:14PM +0800, Kent Gibson wrote:
> > > On Fri, Sep 11, 2020 at 12:53:55PM +0300, Andy Shevchenko wrote:
> > > > On Fri, Sep 11, 2020 at 05:12:49PM +0800, Kent Gibson wrote:
> > > > > On Fri, Sep 11, 2020 at 11:31:09AM +0300, Andy Shevchenko wrote:
> > > > > > On Fri, Sep 11, 2020 at 11:05:39AM +0800, Kent Gibson wrote:
> > > > > > > On Thu, Sep 10, 2020 at 01:19:34PM +0300, Andy Shevchenko wrote:
> > > >
>
> [snip]
> > >
> > > typedef u64 __attribute__((aligned(4))) compat_u64;
> > >
> > > which is bitwise identical - only allowed to 32-bit align.
> >
> > Yes. That's what I meant under "not the same".
> >
> > As far as I understand the alignment makes sense if this type is a part of
> > the uAPI definition. But here we have it completely local. copy_to_user() takes
> > a pointer to a memory without any specific alignment implied.
> >
> > So, what you proposing is basically something like
> >
> > ret = copy_to_user(buf, &ge, compat ?  sizeof(compat) : sizeof(ge));
> >
> > Correct?
> >
>
> That isn't how I would write the copy_to_user(). The size would be
> calculated once, using the linevent_user_size() helper, with
> appropriate documentation as to why this is necessary, and then
> used throughout lineevent_read().
>
> The documentation would mainly be on the lineevent_user_size() function
> itself.
>
> > I don't like the difference between 2nd and 3rd argument. This what looks to me
> > hackish. Variant with explicit compat structure I like more.
> >
>
> Agreed - writing it that way does look pretty nasty.
>
> But my suggestion is actually this:
>
> ret = copy_to_user(buf, &ge, event_size);
>
> I suggested ge_size previously, but event_size might help highlight that
> it isn't always sizeof(ge).
>
> > But if you think it's okay, I will update your way.
> >
>
> I would defer to Bart or Linus, but I think just calculating the
> appropriate size is preferable for this case.
>
> Cheers,
> Kent.

Kent has been producing clean and elegant code so far so I trust him
on code quality issues TBH. Personally in this case however I'd go
with an explicit compat structure as Andy prefers.

I don't have a strong opinion on this so I really am fine either way.

Bartosz
Andy Shevchenko Sept. 14, 2020, 1:04 p.m. UTC | #10
On Mon, Sep 14, 2020 at 4:00 PM Bartosz Golaszewski
<bgolaszewski@baylibre.com> wrote:
> On Sat, Sep 12, 2020 at 4:21 AM Kent Gibson <warthog618@gmail.com> wrote:
> > On Fri, Sep 11, 2020 at 05:28:46PM +0300, Andy Shevchenko wrote:
> > > On Fri, Sep 11, 2020 at 06:17:14PM +0800, Kent Gibson wrote:

...

> > > I don't like the difference between 2nd and 3rd argument. This what looks to me
> > > hackish. Variant with explicit compat structure I like more.
> > >
> >
> > Agreed - writing it that way does look pretty nasty.
> >
> > But my suggestion is actually this:
> >
> > ret = copy_to_user(buf, &ge, event_size);
> >
> > I suggested ge_size previously, but event_size might help highlight that
> > it isn't always sizeof(ge).
> >
> > > But if you think it's okay, I will update your way.
> > >
> >
> > I would defer to Bart or Linus, but I think just calculating the
> > appropriate size is preferable for this case.
> >
> > Cheers,
> > Kent.
>
> Kent has been producing clean and elegant code so far so I trust him
> on code quality issues TBH. Personally in this case however I'd go
> with an explicit compat structure as Andy prefers.
>
> I don't have a strong opinion on this so I really am fine either way.

Since the initial idea was Arnd's and he agreed on Kent's approach, I
will re-do that way.
Andy Shevchenko Sept. 14, 2020, 2:26 p.m. UTC | #11
On Fri, Sep 11, 2020 at 06:20:49PM +0200, Arnd Bergmann wrote:
> > +static ssize_t lineevent_to_user(char __user *buf, struct gpioevent_data *ge)
> > +{
> > +#ifdef __x86_64__
> 
> I would make this "#ifdef CONFIG_IA32_COMPAT" to clarify what this
> is actually checking for.

There is no such option available right now, I prefer to leave as is to make
backporting easier.

> In theory we could add support for
> CONFIG_OABI_COMPAT here as well, not sure if there is a point.
> I recently came across a couple of things that all need the same
> hacks for arm-oabi and x86-32 in principle.
> 
> > +       /* i386 has no padding after 'id' */
> > +       if (in_ia32_syscall()) {
> > +               struct compat_ge {
> > +                       compat_u64      timestamp __packed;
> 
> No need for marking this __packed, it already is.

Yeah, due to a special alignment for compat_u64. I blindly copied from your
proposal.

> > +                       u32             id;
> > +               } cge;
> > +
> > +               if (buf && ge) {
> 
> I think I'd leave out the 'if()' checks here, and require the function
> to be called with valid pointers. It seems odd otherwise to return
> sizeof(cge) from the read() function without having written data.
> 
> Note also that user space may pass a NULL pointer and should
> get back -EFAULT instead of 12 or 16.

OK!

> > -       if (count < sizeof(ge))
> > +       /* When argument is NULL it returns size of the structure in user space */
> > +       ge_size = lineevent_to_user(NULL, NULL);
> > +       if (count < ge_size)
> >                 return -EINVAL;
> 
> Right, I see this is how it's being used, and I'd tend to agree with Kent:
> if you just determine the size dynamically and add a good comment,
> then the rest of the code gets simpler and more logical.

Okay, I will re-do this.
diff mbox series

Patch

diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index e6c9b78adfc2..a6a8384c8255 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -423,6 +423,33 @@  static __poll_t lineevent_poll(struct file *file,
 	return events;
 }
 
+static ssize_t lineevent_to_user(char __user *buf, struct gpioevent_data *ge)
+{
+#ifdef __x86_64__
+	/* i386 has no padding after 'id' */
+	if (in_ia32_syscall()) {
+		struct compat_ge {
+			compat_u64	timestamp __packed;
+			u32		id;
+		} cge;
+
+		if (buf && ge) {
+			cge = (struct compat_ge){ ge->timestamp, ge->id };
+			if (copy_to_user(buf, &cge, sizeof(cge)))
+				return -EFAULT;
+		}
+
+		return sizeof(cge);
+	}
+#endif
+
+	if (buf && ge) {
+		if (copy_to_user(buf, ge, sizeof(*ge)))
+			return -EFAULT;
+	}
+
+	return sizeof(*ge);
+}
 
 static ssize_t lineevent_read(struct file *file,
 			      char __user *buf,
@@ -432,9 +459,12 @@  static ssize_t lineevent_read(struct file *file,
 	struct lineevent_state *le = file->private_data;
 	struct gpioevent_data ge;
 	ssize_t bytes_read = 0;
+	ssize_t ge_size;
 	int ret;
 
-	if (count < sizeof(ge))
+	/* When argument is NULL it returns size of the structure in user space */
+	ge_size = lineevent_to_user(NULL, NULL);
+	if (count < ge_size)
 		return -EINVAL;
 
 	do {
@@ -470,10 +500,11 @@  static ssize_t lineevent_read(struct file *file,
 			break;
 		}
 
-		if (copy_to_user(buf + bytes_read, &ge, sizeof(ge)))
-			return -EFAULT;
-		bytes_read += sizeof(ge);
-	} while (count >= bytes_read + sizeof(ge));
+		ret = lineevent_to_user(buf + bytes_read, &ge);
+		if (ret < 0)
+			return ret;
+		bytes_read += ret;
+	} while (count >= bytes_read + ge_size);
 
 	return bytes_read;
 }