From: Peter Collingbourne <hidden> Date: 2021-08-26 19:46:15
A common implementation of isatty(3) involves calling a ioctl passing
a dummy struct argument and checking whether the syscall failed --
bionic and glibc use TCGETS (passing a struct termios), and musl uses
TIOCGWINSZ (passing a struct winsize). If the FD is a socket, we will
copy sizeof(struct ifreq) bytes of data from the argument and return
-EFAULT if that fails. The result is that the isatty implementations
may return a non-POSIX-compliant value in errno in the case where part
of the dummy struct argument is inaccessible, as both struct termios
and struct winsize are smaller than struct ifreq (at least on arm64).
Although there is usually enough stack space following the argument
on the stack that this did not present a practical problem up to now,
with MTE stack instrumentation it's more likely for the copy to fail,
as the memory following the struct may have a different tag.
Fix the problem by adding an early check for whether the ioctl is a
valid socket ioctl, and return -ENOTTY if it isn't.
Fixes: 44c02a2c3dc5 ("dev_ioctl(): move copyin/copyout to callers")
Link: https://linux-review.googlesource.com/id/I869da6cf6daabc3e4b7b82ac979683ba05e27d4d
Signed-off-by: Peter Collingbourne <redacted>
Cc: <redacted> # 4.19
---
v2:
- simplify check by using _IOC_TYPE()
- move function inline into header
include/linux/netdevice.h | 4 ++++
net/socket.c | 6 +++++-
2 files changed, 9 insertions(+), 1 deletion(-)
@@ -1118,6 +1118,8 @@ static long sock_do_ioctl(struct net *net, struct socket *sock,if(!err&&need_copyout)if(copy_to_user(argp,&ifr,sizeof(structifreq)))return-EFAULT;+}else{+err=-ENOTTY;}returnerr;}
@@ -3306,6 +3308,8 @@ static int compat_ifr_data_ioctl(struct net *net, unsigned int cmd,structifreqifreq;u32data32;+if(!is_socket_ioctl_cmd(cmd))+return-ENOTTY;if(copy_from_user(ifreq.ifr_name,u_ifreq32->ifr_name,IFNAMSIZ))return-EFAULT;if(get_user(data32,&u_ifreq32->ifr_data))
Hello:
This patch was applied to netdev/net.git (refs/heads/master):
On Thu, 26 Aug 2021 12:46:01 -0700 you wrote:
A common implementation of isatty(3) involves calling a ioctl passing
a dummy struct argument and checking whether the syscall failed --
bionic and glibc use TCGETS (passing a struct termios), and musl uses
TIOCGWINSZ (passing a struct winsize). If the FD is a socket, we will
copy sizeof(struct ifreq) bytes of data from the argument and return
-EFAULT if that fails. The result is that the isatty implementations
may return a non-POSIX-compliant value in errno in the case where part
of the dummy struct argument is inaccessible, as both struct termios
and struct winsize are smaller than struct ifreq (at least on arm64).
[...]
From: Jakub Kicinski <kuba@kernel.org> Date: 2021-08-31 16:30:10
On Thu, 26 Aug 2021 12:46:01 -0700 Peter Collingbourne wrote:
quoted hunk
@@ -3306,6 +3308,8 @@ static int compat_ifr_data_ioctl(struct net *net, unsigned int cmd, struct ifreq ifreq; u32 data32;+ if (!is_socket_ioctl_cmd(cmd))+ return -ENOTTY; if (copy_from_user(ifreq.ifr_name, u_ifreq32->ifr_name, IFNAMSIZ)) return -EFAULT; if (get_user(data32, &u_ifreq32->ifr_data))
Hi Peter, when resolving the net -> net-next merge conflict I couldn't
figure out why this chunk is needed. It seems all callers of
compat_ifr_data_ioctl() already made sure it's a socket IOCTL.
Please double check my resolution (tip of net-next) and if this is
indeed unnecessary perhaps send a cleanup? Thanks!
From: David Laight <hidden> Date: 2021-09-01 08:22:52
From: Jakub Kicinski
Sent: 31 August 2021 17:30
On Thu, 26 Aug 2021 12:46:01 -0700 Peter Collingbourne wrote:
quoted
@@ -3306,6 +3308,8 @@ static int compat_ifr_data_ioctl(struct net *net, unsigned int cmd, struct ifreq ifreq; u32 data32;+ if (!is_socket_ioctl_cmd(cmd))+ return -ENOTTY; if (copy_from_user(ifreq.ifr_name, u_ifreq32->ifr_name, IFNAMSIZ)) return -EFAULT; if (get_user(data32, &u_ifreq32->ifr_data))
Hi Peter, when resolving the net -> net-next merge conflict I couldn't
figure out why this chunk is needed. It seems all callers of
compat_ifr_data_ioctl() already made sure it's a socket IOCTL.
Please double check my resolution (tip of net-next) and if this is
indeed unnecessary perhaps send a cleanup? Thanks!
To stop the copy_from_user() faulting when the user buffer
isn't long enough.
In particular for iasatty() on arm with tagged pointers.
David
-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
From: Jakub Kicinski <kuba@kernel.org> Date: 2021-09-01 14:04:02
On Wed, 1 Sep 2021 08:22:42 +0000 David Laight wrote:
From: Jakub Kicinski
quoted
Sent: 31 August 2021 17:30
On Thu, 26 Aug 2021 12:46:01 -0700 Peter Collingbourne wrote:
quoted
@@ -3306,6 +3308,8 @@ static int compat_ifr_data_ioctl(struct net *net, unsigned int cmd, struct ifreq ifreq; u32 data32;+ if (!is_socket_ioctl_cmd(cmd))+ return -ENOTTY; if (copy_from_user(ifreq.ifr_name, u_ifreq32->ifr_name, IFNAMSIZ)) return -EFAULT; if (get_user(data32, &u_ifreq32->ifr_data))
Hi Peter, when resolving the net -> net-next merge conflict I couldn't
figure out why this chunk is needed. It seems all callers of
compat_ifr_data_ioctl() already made sure it's a socket IOCTL.
Please double check my resolution (tip of net-next) and if this is
indeed unnecessary perhaps send a cleanup? Thanks!
To stop the copy_from_user() faulting when the user buffer
isn't long enough.
In particular for iasatty() on arm with tagged pointers.
Let me rephrase. is_socket_ioctl_cmd() is always true here. There were
only two callers, both check cmd is of specific, "sockety" type.
From: Peter Collingbourne <hidden> Date: 2021-09-01 18:01:48
On Wed, Sep 1, 2021 at 7:04 AM Jakub Kicinski [off-list ref] wrote:
On Wed, 1 Sep 2021 08:22:42 +0000 David Laight wrote:
quoted
From: Jakub Kicinski
quoted
Sent: 31 August 2021 17:30
On Thu, 26 Aug 2021 12:46:01 -0700 Peter Collingbourne wrote:
quoted
@@ -3306,6 +3308,8 @@ static int compat_ifr_data_ioctl(struct net *net, unsigned int cmd, struct ifreq ifreq; u32 data32;+ if (!is_socket_ioctl_cmd(cmd))+ return -ENOTTY; if (copy_from_user(ifreq.ifr_name, u_ifreq32->ifr_name, IFNAMSIZ)) return -EFAULT; if (get_user(data32, &u_ifreq32->ifr_data))
Hi Peter, when resolving the net -> net-next merge conflict I couldn't
figure out why this chunk is needed. It seems all callers of
compat_ifr_data_ioctl() already made sure it's a socket IOCTL.
Please double check my resolution (tip of net-next) and if this is
indeed unnecessary perhaps send a cleanup? Thanks!
To stop the copy_from_user() faulting when the user buffer
isn't long enough.
In particular for iasatty() on arm with tagged pointers.
Let me rephrase. is_socket_ioctl_cmd() is always true here. There were
only two callers, both check cmd is of specific, "sockety" type.
I see, it looks like we don't need the check on the compat path then.
I can send a followup to clean this up but given that I got a comment
from another reviewer saying that we should try to make the native and
compat paths as similar as possible, maybe it isn't too bad to leave
things as is?
Peter
From: Jakub Kicinski <kuba@kernel.org> Date: 2021-09-01 23:20:16
On Wed, 1 Sep 2021 11:01:32 -0700 Peter Collingbourne wrote:
quoted
quoted
To stop the copy_from_user() faulting when the user buffer
isn't long enough.
In particular for iasatty() on arm with tagged pointers.
Let me rephrase. is_socket_ioctl_cmd() is always true here. There were
only two callers, both check cmd is of specific, "sockety" type.
I see, it looks like we don't need the check on the compat path then.
I can send a followup to clean this up but given that I got a comment
from another reviewer saying that we should try to make the native and
compat paths as similar as possible, maybe it isn't too bad to leave
things as is?
I have a weak preference to get rid of it, the code is a little
complex and extra dead code makes it harder to follow, but up to you.
IMO the "right place" for the check is:
static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg)
[...]
default:
/* --> here <-- */
err = sock_do_ioctl(net, sock, cmd, arg);
break;
Since that's the point where we take all the remaining cmd values and
call a function which assumes struct ifreq.
Compat code does not have a default statement.
But as I said no big deal, feel free to ignore.