2023-04-14 15:28:02

by Luca Vizzarro

[permalink] [raw]
Subject: [PATCH v2 1/5] fcntl: Cast commands with int args explicitly

According to the fcntl API specification commands that expect an
integer, hence not a pointer, always take an int and not long. In
order to avoid access to undefined bits, we should explicitly cast
the argument to int.

Cc: Alexander Viro <[email protected]>
Cc: Christian Brauner <[email protected]>
Cc: Jeff Layton <[email protected]>
Cc: Chuck Lever <[email protected]>
Cc: Kevin Brodsky <[email protected]>
Cc: Vincenzo Frascino <[email protected]>
Cc: Szabolcs Nagy <[email protected]>
Cc: "Theodore Ts'o" <[email protected]>
Cc: David Laight <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: [email protected]
Cc: [email protected]
Signed-off-by: Luca Vizzarro <[email protected]>
---
fs/fcntl.c | 29 +++++++++++++++--------------
include/linux/fs.h | 2 +-
2 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/fs/fcntl.c b/fs/fcntl.c
index b622be119706..e871009f6c88 100644
--- a/fs/fcntl.c
+++ b/fs/fcntl.c
@@ -34,7 +34,7 @@

#define SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | O_DIRECT | O_NOATIME)

-static int setfl(int fd, struct file * filp, unsigned long arg)
+static int setfl(int fd, struct file * filp, unsigned int arg)
{
struct inode * inode = file_inode(filp);
int error = 0;
@@ -112,11 +112,11 @@ void __f_setown(struct file *filp, struct pid *pid, enum pid_type type,
}
EXPORT_SYMBOL(__f_setown);

-int f_setown(struct file *filp, unsigned long arg, int force)
+int f_setown(struct file *filp, int who, int force)
{
enum pid_type type;
struct pid *pid = NULL;
- int who = arg, ret = 0;
+ int ret = 0;

type = PIDTYPE_TGID;
if (who < 0) {
@@ -317,28 +317,29 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
struct file *filp)
{
void __user *argp = (void __user *)arg;
+ int argi = (int)arg;
struct flock flock;
long err = -EINVAL;

switch (cmd) {
case F_DUPFD:
- err = f_dupfd(arg, filp, 0);
+ err = f_dupfd(argi, filp, 0);
break;
case F_DUPFD_CLOEXEC:
- err = f_dupfd(arg, filp, O_CLOEXEC);
+ err = f_dupfd(argi, filp, O_CLOEXEC);
break;
case F_GETFD:
err = get_close_on_exec(fd) ? FD_CLOEXEC : 0;
break;
case F_SETFD:
err = 0;
- set_close_on_exec(fd, arg & FD_CLOEXEC);
+ set_close_on_exec(fd, argi & FD_CLOEXEC);
break;
case F_GETFL:
err = filp->f_flags;
break;
case F_SETFL:
- err = setfl(fd, filp, arg);
+ err = setfl(fd, filp, argi);
break;
#if BITS_PER_LONG != 32
/* 32-bit arches must use fcntl64() */
@@ -375,7 +376,7 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
force_successful_syscall_return();
break;
case F_SETOWN:
- err = f_setown(filp, arg, 1);
+ err = f_setown(filp, argi, 1);
break;
case F_GETOWN_EX:
err = f_getown_ex(filp, arg);
@@ -391,28 +392,28 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
break;
case F_SETSIG:
/* arg == 0 restores default behaviour. */
- if (!valid_signal(arg)) {
+ if (!valid_signal(argi)) {
break;
}
err = 0;
- filp->f_owner.signum = arg;
+ filp->f_owner.signum = argi;
break;
case F_GETLEASE:
err = fcntl_getlease(filp);
break;
case F_SETLEASE:
- err = fcntl_setlease(fd, filp, arg);
+ err = fcntl_setlease(fd, filp, argi);
break;
case F_NOTIFY:
- err = fcntl_dirnotify(fd, filp, arg);
+ err = fcntl_dirnotify(fd, filp, argi);
break;
case F_SETPIPE_SZ:
case F_GETPIPE_SZ:
- err = pipe_fcntl(filp, cmd, arg);
+ err = pipe_fcntl(filp, cmd, argi);
break;
case F_ADD_SEALS:
case F_GET_SEALS:
- err = memfd_fcntl(filp, cmd, arg);
+ err = memfd_fcntl(filp, cmd, argi);
break;
case F_GET_RW_HINT:
case F_SET_RW_HINT:
diff --git a/include/linux/fs.h b/include/linux/fs.h
index c85916e9f7db..8da79822dbba 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1050,7 +1050,7 @@ extern void fasync_free(struct fasync_struct *);
extern void kill_fasync(struct fasync_struct **, int, int);

extern void __f_setown(struct file *filp, struct pid *, enum pid_type, int force);
-extern int f_setown(struct file *filp, unsigned long arg, int force);
+extern int f_setown(struct file *filp, int who, int force);
extern void f_delown(struct file *filp);
extern pid_t f_getown(struct file *filp);
extern int send_sigurg(struct fown_struct *fown);
--
2.34.1


2023-04-14 15:49:38

by Al Viro

[permalink] [raw]
Subject: Re: [PATCH v2 1/5] fcntl: Cast commands with int args explicitly

On Fri, Apr 14, 2023 at 04:24:55PM +0100, Luca Vizzarro wrote:
> void __user *argp = (void __user *)arg;
> + int argi = (int)arg;

Strictly speaking, conversion from unsigned long to int is
an undefined behaviour, unless the value fits into the
range representable by int ;-)

> case F_SETFD:
> err = 0;
> - set_close_on_exec(fd, arg & FD_CLOEXEC);
> + set_close_on_exec(fd, argi & FD_CLOEXEC);

Why?

> case F_SETSIG:
> /* arg == 0 restores default behaviour. */
> - if (!valid_signal(arg)) {
> + if (!valid_signal(argi)) {

Why???

> break;
> }
> err = 0;
> - filp->f_owner.signum = arg;
> + filp->f_owner.signum = argi;
> break;

These two are clearly bogus and I'd like to see more details
on the series rationale, please.

2023-04-17 09:41:36

by Mark Rutland

[permalink] [raw]
Subject: Re: [PATCH v2 1/5] fcntl: Cast commands with int args explicitly

On Fri, Apr 14, 2023 at 04:46:31PM +0100, Al Viro wrote:
> On Fri, Apr 14, 2023 at 04:24:55PM +0100, Luca Vizzarro wrote:
> > void __user *argp = (void __user *)arg;
> > + int argi = (int)arg;
>
> Strictly speaking, conversion from unsigned long to int is
> an undefined behaviour, unless the value fits into the
> range representable by int ;-)
>
> > case F_SETFD:
> > err = 0;
> > - set_close_on_exec(fd, arg & FD_CLOEXEC);
> > + set_close_on_exec(fd, argi & FD_CLOEXEC);
>
> Why?
>
> > case F_SETSIG:
> > /* arg == 0 restores default behaviour. */
> > - if (!valid_signal(arg)) {
> > + if (!valid_signal(argi)) {
>
> Why???
>
> > break;
> > }
> > err = 0;
> > - filp->f_owner.signum = arg;
> > + filp->f_owner.signum = argi;
> > break;
>
> These two are clearly bogus and I'd like to see more details
> on the series rationale, please.

I agree the first isn't necessary, but I don't think the second is bogus, since
valid_signal() takes an unsigned long and the man page for F_SETSIG says that
the argument is an int:

https://man7.org/linux/man-pages/man2/fcntl.2.html

... though arguably that could be a bug in the man page.

The cover letter really should have quoted the description that Szabolcs wote
at:

https://lore.kernel.org/linux-api/Y1%[email protected]/

The gist being that where the calling convention leaves narrowing to callees
(as is the case on arm64 with our "AAPCS64" calling convention), if the caller
passes a type which is narrower than a register, the upper bits of that
register may contain junk.

So e.g. for F_SETSIG, if the userspace will try to pass some 32-bit value,
leaving bits 63:32 of the argument register containing arbitrary junk. Then
here we interprert the value as an unsigned long, considering that junk as part
of the argument. Then valid_signal(arg) may end up rejecting the argument due
to the junk uper bits, which is surprising to the caller as from its PoV it
passed a 32-bit value in the correct way.

So either:

* That's a documentation bug, and userspce needs to treat the agument to
F_SETSIG as an unsigned long.

* The kernel needs to narrow the argument to an int (if required by the calling
convention) to prevent that.

Does that make sense, or have I missed the point you were making?

Thanks,
Mark.

2023-04-18 11:07:44

by Luca Vizzarro

[permalink] [raw]
Subject: Re: [PATCH v2 1/5] fcntl: Cast commands with int args explicitly

On 14/04/2023 16:46, Al Viro wrote

> Why?
> ...
> Why???
> ...
> These two are clearly bogus and I'd like to see more details
> on the series rationale, please.

Mark preceded me with his reply, which is perfectly summarising
the whole point of this series. – Thank you Mark!

As for FD_SETFD, yes it's not necessary. The only reason I
changed the variable was to denote the argument as being
classified as an `int` as per the man page. If I were not to
change it, it would have been the only command with an `int`
argument not to use `argi`. Therefore it's also for
consistency's sake.

Hope this helps.

Best,
Luca