LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [CFT][PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
@ 2019-02-08  2:34 Eric W. Biederman
  2019-02-08 21:57 ` Alan Stern
  0 siblings, 1 reply; 14+ messages in thread
From: Eric W. Biederman @ 2019-02-08  2:34 UTC (permalink / raw)
  To: linux-usb; +Cc: Greg Kroah-Hartman, Alan Stern, Oliver Neukum, linux-kernel


The usb support for asyncio encoded one of it's values in the wrong
field.  It should have used si_value but instead used si_addr which is
not present in the _rt union member of struct siginfo.

The result is a POSIX and glibc incompatible encoding of fields in
struct siginfo with si_code of SI_ASYNCIO.  This makes it impossible
to look at a struct siginfo with si_code set to SI_ASYNCIO and without
context properly decode it.  Which unfortunately means that
copy_siginfo_to_user32 can't handle the compat issues this unfortunate
choice in encodings brings up.

Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
dedicated function for this one specific case.  There are no other
users of kill_pid_info_as_cred so this specialization should have no
impact on the amont of code in the kernel.  Have kill_pid_usb_asyncio
take instead of a siginfo_t which is difficult error prone 3
arguments, a signal number, an errno value, and an address enconded as
a sigval_t.  The encoding as a sigval_t allows the caller to deal with
the compat issues before calling kill_pid_info_as_cred.

Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
the pointer value at the in si_pid (instead of si_addr) and get
the same binary result when the structure is copied to user space
and when the structure is copied field by field.

The usb code is updated to track if the values it passes into
kill_pid_usb_asyncio were passed to it from a native userspace
or from at compat user space.  To allow a proper conversion
of the addresses.

Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Oliver Neukum <oneukum@suse.com>
Fixes: v2.3.39
Cc: stable@vger.kernel.org
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
---

Can I get someone to test this code?  I just discovered that the
usb code is filling in struct siginfo wrong and copy_siginfo_to_user32
can't possibly get this correct without some help.

I think I have coded up a working fix.  But I don't have a setup
where I can test this.

 drivers/usb/core/devio.c     | 45 +++++++++++------------
 include/linux/sched/signal.h |  2 +-
 kernel/signal.c              | 69 +++++++++++++++++++++++++++++++-----
 3 files changed, 83 insertions(+), 33 deletions(-)

diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index d65566341dd1..d1a53f760f00 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -63,7 +63,7 @@ struct usb_dev_state {
 	unsigned int discsignr;
 	struct pid *disc_pid;
 	const struct cred *cred;
-	void __user *disccontext;
+	sigval_t disccontext;
 	unsigned long ifclaimed;
 	u32 disabled_bulk_eps;
 	bool privileges_dropped;
@@ -94,6 +94,7 @@ struct async {
 	struct usb_memory *usbm;
 	unsigned int mem_usage;
 	int status;
+	u8 compat_userurb;
 	u8 bulk_addr;
 	u8 bulk_status;
 };
@@ -582,22 +583,24 @@ static void async_completed(struct urb *urb)
 {
 	struct async *as = urb->context;
 	struct usb_dev_state *ps = as->ps;
-	struct kernel_siginfo sinfo;
 	struct pid *pid = NULL;
 	const struct cred *cred = NULL;
 	unsigned long flags;
-	int signr;
+	sigval_t addr;
+	int signr, errno;
 
 	spin_lock_irqsave(&ps->lock, flags);
 	list_move_tail(&as->asynclist, &ps->async_completed);
 	as->status = urb->status;
 	signr = as->signr;
 	if (signr) {
-		clear_siginfo(&sinfo);
-		sinfo.si_signo = as->signr;
-		sinfo.si_errno = as->status;
-		sinfo.si_code = SI_ASYNCIO;
-		sinfo.si_addr = as->userurb;
+		errno = as->status;
+#ifdef CONFIG_COMPAT
+		if (as->compat_userurb)
+			addr.sival_int = ptr_to_compat(as->userurb);
+		else
+#endif
+			addr.sival_ptr = as->userurb;
 		pid = get_pid(as->pid);
 		cred = get_cred(as->cred);
 	}
@@ -615,7 +618,7 @@ static void async_completed(struct urb *urb)
 	spin_unlock_irqrestore(&ps->lock, flags);
 
 	if (signr) {
-		kill_pid_info_as_cred(sinfo.si_signo, &sinfo, pid, cred);
+		kill_pid_usb_asyncio(signr, errno, addr, pid, cred);
 		put_pid(pid);
 		put_cred(cred);
 	}
@@ -1427,7 +1430,7 @@ find_memory_area(struct usb_dev_state *ps, const struct usbdevfs_urb *uurb)
 
 static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb,
 			struct usbdevfs_iso_packet_desc __user *iso_frame_desc,
-			void __user *arg)
+			void __user *arg, bool compat)
 {
 	struct usbdevfs_iso_packet_desc *isopkt = NULL;
 	struct usb_host_endpoint *ep;
@@ -1729,6 +1732,7 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
 	isopkt = NULL;
 	as->ps = ps;
 	as->userurb = arg;
+	as->compat_userurb = compat;
 	if (as->usbm) {
 		unsigned long uurb_start = (unsigned long)uurb->buffer;
 
@@ -1809,7 +1813,7 @@ static int proc_submiturb(struct usb_dev_state *ps, void __user *arg)
 
 	return proc_do_submiturb(ps, &uurb,
 			(((struct usbdevfs_urb __user *)arg)->iso_frame_desc),
-			arg);
+			arg, false);
 }
 
 static int proc_unlinkurb(struct usb_dev_state *ps, void __user *arg)
@@ -1979,7 +1983,7 @@ static int proc_disconnectsignal_compat(struct usb_dev_state *ps, void __user *a
 	if (copy_from_user(&ds, arg, sizeof(ds)))
 		return -EFAULT;
 	ps->discsignr = ds.signr;
-	ps->disccontext = compat_ptr(ds.context);
+	ps->disccontext.sival_int = ds.context;
 	return 0;
 }
 
@@ -2013,7 +2017,7 @@ static int proc_submiturb_compat(struct usb_dev_state *ps, void __user *arg)
 
 	return proc_do_submiturb(ps, &uurb,
 			((struct usbdevfs_urb32 __user *)arg)->iso_frame_desc,
-			arg);
+			arg, true);
 }
 
 static int processcompl_compat(struct async *as, void __user * __user *arg)
@@ -2094,7 +2098,7 @@ static int proc_disconnectsignal(struct usb_dev_state *ps, void __user *arg)
 	if (copy_from_user(&ds, arg, sizeof(ds)))
 		return -EFAULT;
 	ps->discsignr = ds.signr;
-	ps->disccontext = ds.context;
+	ps->disccontext.sival_ptr = ds.context;
 	return 0;
 }
 
@@ -2616,22 +2620,15 @@ const struct file_operations usbdev_file_operations = {
 static void usbdev_remove(struct usb_device *udev)
 {
 	struct usb_dev_state *ps;
-	struct kernel_siginfo sinfo;
 
 	while (!list_empty(&udev->filelist)) {
 		ps = list_entry(udev->filelist.next, struct usb_dev_state, list);
 		destroy_all_async(ps);
 		wake_up_all(&ps->wait);
 		list_del_init(&ps->list);
-		if (ps->discsignr) {
-			clear_siginfo(&sinfo);
-			sinfo.si_signo = ps->discsignr;
-			sinfo.si_errno = EPIPE;
-			sinfo.si_code = SI_ASYNCIO;
-			sinfo.si_addr = ps->disccontext;
-			kill_pid_info_as_cred(ps->discsignr, &sinfo,
-					ps->disc_pid, ps->cred);
-		}
+		if (ps->discsignr)
+			kill_pid_usb_asyncio(ps->discsignr, EPIPE, ps->disccontext,
+					     ps->disc_pid, ps->cred);
 	}
 }
 
diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index e31d68204b23..80a51555a7e1 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -330,7 +330,7 @@ extern void force_sigsegv(int sig);
 extern int force_sig_info(struct kernel_siginfo *);
 extern int __kill_pgrp_info(int sig, struct kernel_siginfo *info, struct pid *pgrp);
 extern int kill_pid_info(int sig, struct kernel_siginfo *info, struct pid *pid);
-extern int kill_pid_info_as_cred(int, struct kernel_siginfo *, struct pid *,
+extern int kill_pid_usb_asyncio(int sig, int errno, sigval_t addr, struct pid *,
 				const struct cred *);
 extern int kill_pgrp(struct pid *pid, int sig, int priv);
 extern int kill_pid(struct pid *pid, int sig, int priv);
diff --git a/kernel/signal.c b/kernel/signal.c
index 3d522dd2858b..acc43c9a5fd3 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1437,13 +1437,44 @@ static inline bool kill_as_cred_perm(const struct cred *cred,
 	       uid_eq(cred->uid, pcred->uid);
 }
 
-/* like kill_pid_info(), but doesn't use uid/euid of "current" */
-int kill_pid_info_as_cred(int sig, struct kernel_siginfo *info, struct pid *pid,
-			 const struct cred *cred)
+/*
+ * The usb asyncio usage of siginfo is wrong.  The glibc support
+ * for asyncio which uses SI_ASYNCIO assumes the layout is SIL_RT.
+ * AKA after the generic fields:
+ *	kernel_pid_t	si_pid;
+ *	kernel_uid32_t	si_uid;
+ *	sigval_t	si_value;
+ *
+ * Unfortunately when usb generates SI_ASYNCIO it assumes the layout
+ * after the generic fields is:
+ *	void __user 	*si_addr;
+ *
+ * This is a practical problem when there is a 64bit big endian kernel
+ * and a 32bit userspace.  As the 32bit address will encoded in the low
+ * 32bits of the pointer.  Those low 32bits will be stored at higher
+ * address than appear in a 32 bit pointer.  So userspace will not
+ * see the address it was expecting for it's completions.
+ *
+ * There is nothing in the encoding that can allow
+ * copy_siginfo_to_user32 to detect this confusion of formats, so
+ * handle this by requiring the caller of kill_pid_usb_asyncio to
+ * notice when this situration takes place and to store the 32bit
+ * pointer in sival_int, instead of sival_addr of the sigval_t addr
+ * parameter.
+ */
+int kill_pid_usb_asyncio(int sig, int errno, sigval_t addr,
+			 struct pid *pid, const struct cred *cred)
 {
-	int ret = -EINVAL;
+	struct kernel_siginfo info;
 	struct task_struct *p;
 	unsigned long flags;
+	int ret = -EINVAL;
+
+	clear_siginfo(&info);
+	info.si_signo = sig;
+	info.si_errno = errno;
+	info.si_code = SI_ASYNCIO;
+	*((sigval_t *)&info.si_pid) = addr;
 
 	if (!valid_signal(sig))
 		return ret;
@@ -1454,17 +1485,17 @@ int kill_pid_info_as_cred(int sig, struct kernel_siginfo *info, struct pid *pid,
 		ret = -ESRCH;
 		goto out_unlock;
 	}
-	if (si_fromuser(info) && !kill_as_cred_perm(cred, p)) {
+	if (!kill_as_cred_perm(cred, p)) {
 		ret = -EPERM;
 		goto out_unlock;
 	}
-	ret = security_task_kill(p, info, sig, cred);
+	ret = security_task_kill(p, &info, sig, cred);
 	if (ret)
 		goto out_unlock;
 
 	if (sig) {
 		if (lock_task_sighand(p, &flags)) {
-			ret = __send_signal(sig, info, p, PIDTYPE_TGID, 0);
+			ret = __send_signal(sig, &info, p, PIDTYPE_TGID, 0);
 			unlock_task_sighand(p, &flags);
 		} else
 			ret = -ESRCH;
@@ -1473,7 +1504,7 @@ int kill_pid_info_as_cred(int sig, struct kernel_siginfo *info, struct pid *pid,
 	rcu_read_unlock();
 	return ret;
 }
-EXPORT_SYMBOL_GPL(kill_pid_info_as_cred);
+EXPORT_SYMBOL_GPL(kill_pid_usb_asyncio);
 
 /*
  * kill_something_info() interprets pid in interesting ways just like kill(2).
@@ -4318,6 +4349,28 @@ static inline void siginfo_buildtime_checks(void)
 	CHECK_OFFSET(si_syscall);
 	CHECK_OFFSET(si_arch);
 #undef CHECK_OFFSET
+
+	/* usb asyncio */
+	BUILD_BUG_ON(offsetof(struct siginfo, si_pid) !=
+		     offsetof(struct siginfo, si_addr));
+	if (sizeof(int) == sizeof(void __user *)) {
+		BUILD_BUG_ON(sizeof_field(struct siginfo, si_pid) !=
+			     sizeof(void __user *));
+	} else {
+		BUILD_BUG_ON((sizeof_field(struct siginfo, si_pid) +
+			      sizeof_field(struct siginfo, si_uid)) !=
+			     sizeof(void __user *));
+		BUILD_BUG_ON(offsetofend(struct siginfo, si_pid) !=
+			     offsetof(struct siginfo, si_uid));
+	}
+#ifdef CONFIG_COMPAT
+	BUILD_BUG_ON(offsetof(struct compat_siginfo, si_pid) !=
+		     offsetof(struct compat_siginfo, si_addr));
+	BUILD_BUG_ON(sizeof_field(struct compat_siginfo, si_pid) !=
+		     sizeof(compat_uptr_t));
+	BUILD_BUG_ON(sizeof_field(struct compat_siginfo, si_pid) !=
+		     sizeof_field(struct siginfo, si_pid));
+#endif
 }
 
 void __init signals_init(void)
-- 
2.17.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [CFT][PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-02-08  2:34 [CFT][PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio Eric W. Biederman
@ 2019-02-08 21:57 ` Alan Stern
  2019-05-18  1:38   ` Eric W. Biederman
  0 siblings, 1 reply; 14+ messages in thread
From: Alan Stern @ 2019-02-08 21:57 UTC (permalink / raw)
  To: Eric W. Biederman
  Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

[-- Attachment #1: Type: TEXT/PLAIN, Size: 2797 bytes --]

On Thu, 7 Feb 2019, Eric W. Biederman wrote:

> The usb support for asyncio encoded one of it's values in the wrong
> field.  It should have used si_value but instead used si_addr which is
> not present in the _rt union member of struct siginfo.
> 
> The result is a POSIX and glibc incompatible encoding of fields in
> struct siginfo with si_code of SI_ASYNCIO.  This makes it impossible
> to look at a struct siginfo with si_code set to SI_ASYNCIO and without
> context properly decode it.  Which unfortunately means that
> copy_siginfo_to_user32 can't handle the compat issues this unfortunate
> choice in encodings brings up.
> 
> Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
> dedicated function for this one specific case.  There are no other
> users of kill_pid_info_as_cred so this specialization should have no
> impact on the amont of code in the kernel.  Have kill_pid_usb_asyncio
> take instead of a siginfo_t which is difficult error prone 3
> arguments, a signal number, an errno value, and an address enconded as
> a sigval_t.  The encoding as a sigval_t allows the caller to deal with
> the compat issues before calling kill_pid_info_as_cred.
> 
> Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
> the pointer value at the in si_pid (instead of si_addr) and get
> the same binary result when the structure is copied to user space
> and when the structure is copied field by field.
> 
> The usb code is updated to track if the values it passes into
> kill_pid_usb_asyncio were passed to it from a native userspace
> or from at compat user space.  To allow a proper conversion
> of the addresses.
> 
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: linux-usb@vger.kernel.org
> Cc: Alan Stern <stern@rowland.harvard.edu>
> Cc: Oliver Neukum <oneukum@suse.com>
> Fixes: v2.3.39
> Cc: stable@vger.kernel.org
> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
> ---
> 
> Can I get someone to test this code?  I just discovered that the
> usb code is filling in struct siginfo wrong and copy_siginfo_to_user32
> can't possibly get this correct without some help.
> 
> I think I have coded up a working fix.  But I don't have a setup
> where I can test this.

Eric:

You should be able to test this patch by running the attached 
program.  It takes one argument, the pathname to a USB device file.  
For example, on my system:

# ./usbsig /dev/bus/usb/001/001
Got signal 10, signo 10 errno 0 code -4

I don't know exactly what you're looking for, but it should be pretty 
easy to modify the test program however you want.

If you need to test the compatibility mode specifically, I can do that
here -- I'm running a 32-bit userspace on a 64-bit kernel.  But you'll
have to tell me exactly what test code to run.

Alan Stern

[-- Attachment #2: Type: TEXT/plain, Size: 1791 bytes --]

/* usbsig.c -- test USB async signal delivery */

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/usb/ch9.h>
#include <linux/usbdevice_fs.h>

void handler(int sig, siginfo_t *info , void *ucontext)
{
	ucontext_t *con = (ucontext_t *) ucontext;

	printf("Got signal %d, signo %d errno %d code %d\n",
			sig, info->si_signo, info->si_errno, info->si_code);
}

int main(int argc, char **argv)
{
	char *devfilename;
	int fd;
	int rc;
	struct sigaction act;
	struct usbdevfs_urb urb;
	struct usb_ctrlrequest *req;
	void *ptr;
	char buf[80];

	if (argc != 2) {
		fprintf(stderr, "Usage: usbsig device-file-name\n");
		return 1;
	}

	devfilename = argv[1];
	fd = open(devfilename, O_RDWR);
	if (fd == -1) {
		perror("Error opening device file");
		return 1;
	}

	act.sa_sigaction = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;

	rc = sigaction(SIGUSR1, &act, NULL);
	if (rc == -1) {
		perror("Error in sigaction");
		return 1;
	}

	memset(&urb, 0, sizeof(urb));
	urb.type = USBDEVFS_URB_TYPE_CONTROL;
	urb.endpoint = USB_DIR_IN | 0;
	urb.buffer = buf;
	urb.buffer_length = sizeof(buf);
	urb.signr = SIGUSR1;

	req = (struct usb_ctrlrequest *) buf;
	req->bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
	req->bRequest = USB_REQ_GET_DESCRIPTOR;
	req->wValue = USB_DT_DEVICE << 8;
	req->wIndex = 0;
	req->wLength = sizeof(buf) - sizeof(*req);

	rc = ioctl(fd, USBDEVFS_SUBMITURB, &urb);
	if (rc == -1) {
		perror("Error in SUBMITURB ioctl");
		return 1;
	}

	rc = ioctl(fd, USBDEVFS_REAPURB, &ptr);
	if (rc == -1) {
		perror("Error in REAPURB ioctl");
		return 1;
	}

	close(fd);
	return 0;
}

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [CFT][PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-02-08 21:57 ` Alan Stern
@ 2019-05-18  1:38   ` Eric W. Biederman
  2019-05-18 15:20     ` Alan Stern
  0 siblings, 1 reply; 14+ messages in thread
From: Eric W. Biederman @ 2019-05-18  1:38 UTC (permalink / raw)
  To: Alan Stern; +Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3767 bytes --]

Alan Stern <stern@rowland.harvard.edu> writes:

> On Thu, 7 Feb 2019, Eric W. Biederman wrote:
>
>> The usb support for asyncio encoded one of it's values in the wrong
>> field.  It should have used si_value but instead used si_addr which is
>> not present in the _rt union member of struct siginfo.
>> 
>> The result is a POSIX and glibc incompatible encoding of fields in
>> struct siginfo with si_code of SI_ASYNCIO.  This makes it impossible
>> to look at a struct siginfo with si_code set to SI_ASYNCIO and without
>> context properly decode it.  Which unfortunately means that
>> copy_siginfo_to_user32 can't handle the compat issues this unfortunate
>> choice in encodings brings up.
>> 
>> Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
>> dedicated function for this one specific case.  There are no other
>> users of kill_pid_info_as_cred so this specialization should have no
>> impact on the amont of code in the kernel.  Have kill_pid_usb_asyncio
>> take instead of a siginfo_t which is difficult error prone 3
>> arguments, a signal number, an errno value, and an address enconded as
>> a sigval_t.  The encoding as a sigval_t allows the caller to deal with
>> the compat issues before calling kill_pid_info_as_cred.
>> 
>> Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
>> the pointer value at the in si_pid (instead of si_addr) and get
>> the same binary result when the structure is copied to user space
>> and when the structure is copied field by field.
>> 
>> The usb code is updated to track if the values it passes into
>> kill_pid_usb_asyncio were passed to it from a native userspace
>> or from at compat user space.  To allow a proper conversion
>> of the addresses.
>> 
>> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
>> Cc: linux-usb@vger.kernel.org
>> Cc: Alan Stern <stern@rowland.harvard.edu>
>> Cc: Oliver Neukum <oneukum@suse.com>
>> Fixes: v2.3.39
>> Cc: stable@vger.kernel.org
>> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
>> ---
>> 
>> Can I get someone to test this code?  I just discovered that the
>> usb code is filling in struct siginfo wrong and copy_siginfo_to_user32
>> can't possibly get this correct without some help.
>> 
>> I think I have coded up a working fix.  But I don't have a setup
>> where I can test this.
>
> Eric:
>
> You should be able to test this patch by running the attached 
> program.  It takes one argument, the pathname to a USB device file.  
> For example, on my system:
>
> # ./usbsig /dev/bus/usb/001/001
> Got signal 10, signo 10 errno 0 code -4
>
> I don't know exactly what you're looking for, but it should be pretty 
> easy to modify the test program however you want.
>
> If you need to test the compatibility mode specifically, I can do that
> here -- I'm running a 32-bit userspace on a 64-bit kernel.  But you'll
> have to tell me exactly what test code to run.

Wow I got a little distracted but now I am back to this.

Using your test program I was able to test the basics of this.

I found one bug in my patch where I was missing a memset.  So I have
corrected that, and reorganized the patch a little bit.

I have not figured out how to trigger a usb disconnect so I have not
tested that.

The big thing I have not been able to test is running a 64bit big-endian
kernel with a 32bit user space.  My modified version of your test
program should report "Bad" without my patch, and should report "Good"
with it.

Is there any chance you can test that configuration?  I could not figure
out how to get a 64bit big-endian system running in qemu, and I don't
have the necessary hardware so I was not able to test that at all.  As
that is the actual bug I am still hoping someone can test it.

Thank you,
Eric Biederman
---

[-- Attachment #2: usbsig.c --]
[-- Type: text/plain, Size: 2494 bytes --]

/* usbsig.c -- test USB async signal delivery */

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/usb/ch9.h>
#include <linux/usbdevice_fs.h>

static struct usbdevfs_urb urb;
static struct usbdevfs_disconnectsignal ds;

void urb_handler(int sig, siginfo_t *info , void *ucontext)
{
	printf("Got signal %d, signo %d errno %d code %d addr: %p urb: %p\n",
	       sig, info->si_signo, info->si_errno, info->si_code,
	       info->si_addr, &urb);

	printf("%s\n", (info->si_addr == &urb) ? "Good" : "Bad");
}

void ds_handler(int sig, siginfo_t *info , void *ucontext)
{
	printf("Got signal %d, signo %d errno %d code %d addr: %p ds: %p\n",
	       sig, info->si_signo, info->si_errno, info->si_code,
	       info->si_addr, &ds);

	printf("%s\n", (info->si_addr == &ds) ? "Good" : "Bad");
}

int main(int argc, char **argv)
{
	char *devfilename;
	int fd;
	int rc;
	struct sigaction act;
	struct usb_ctrlrequest *req;
	void *ptr;
	char buf[80];

	if (argc != 2) {
		fprintf(stderr, "Usage: usbsig device-file-name\n");
		return 1;
	}

	devfilename = argv[1];
	fd = open(devfilename, O_RDWR);
	if (fd == -1) {
		perror("Error opening device file");
		return 1;
	}

	act.sa_sigaction = urb_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;

	rc = sigaction(SIGUSR1, &act, NULL);
	if (rc == -1) {
		perror("Error in sigaction");
		return 1;
	}

	act.sa_sigaction = ds_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;

	rc = sigaction(SIGUSR2, &act, NULL);
	if (rc == -1) {
		perror("Error in sigaction");
		return 1;
	}

	memset(&urb, 0, sizeof(urb));
	urb.type = USBDEVFS_URB_TYPE_CONTROL;
	urb.endpoint = USB_DIR_IN | 0;
	urb.buffer = buf;
	urb.buffer_length = sizeof(buf);
	urb.signr = SIGUSR1;

	req = (struct usb_ctrlrequest *) buf;
	req->bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
	req->bRequest = USB_REQ_GET_DESCRIPTOR;
	req->wValue = USB_DT_DEVICE << 8;
	req->wIndex = 0;
	req->wLength = sizeof(buf) - sizeof(*req);

	rc = ioctl(fd, USBDEVFS_SUBMITURB, &urb);
	if (rc == -1) {
		perror("Error in SUBMITURB ioctl");
		return 1;
	}

	rc = ioctl(fd, USBDEVFS_REAPURB, &ptr);
	if (rc == -1) {
		perror("Error in REAPURB ioctl");
		return 1;
	}

	memset(&ds, 0, sizeof(ds));
	ds.signr = SIGUSR2;
	ds.context = &ds;
	rc = ioctl(fd, USBDEVFS_DISCSIGNAL, &ds);
	if (rc == -1) {
		perror("Error in DISCSIGNAL ioctl");
		return 1;
	}

	close(fd);
	return 0;
}


[-- Attachment #3: 0001-signal-usb-Replace-kill_pid_info_as_cred-with-kill_p.patch --]
[-- Type: text/plain, Size: 12155 bytes --]

From 0045109b65f32f4228072359282d2f8613fc8964 Mon Sep 17 00:00:00 2001
From: "Eric W. Biederman" <ebiederm@xmission.com>
Date: Thu, 7 Feb 2019 19:44:12 -0600
Subject: [PATCH] signal/usb: Replace kill_pid_info_as_cred with
 kill_pid_usb_asyncio

The usb support for asyncio encoded one of it's values in the wrong
field.  It should have used si_value but instead used si_addr which is
not present in the _rt union member of struct siginfo.

The result is a POSIX and glibc incompatible encoding of fields in
struct siginfo with si_code of SI_ASYNCIO.  This makes it impossible
to look at a struct siginfo with si_code set to SI_ASYNCIO and without
context properly decode it.  Which unfortunately means that
copy_siginfo_to_user32 can't handle the compat issues this unfortunate
choice in encodings brings up.

Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
dedicated function for this one specific case.  There are no other
users of kill_pid_info_as_cred so this specialization should have no
impact on the amount of code in the kernel.  Have kill_pid_usb_asyncio
take instead of a siginfo_t which is difficult and error prone, 3
arguments, a signal number, an errno value, and an address enconded as
a sigval_t.  The encoding of the address as a sigval_t allows the
caller to deal with the compat issues before calling
kill_pid_info_as_cred.

Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
the pointer value at the in si_pid (instead of si_addr).  That is the
code now verifies that si_pid and si_addr always occur at the same
location.  Further the code veries that for native structures a value
placed in si_pid and spilling into si_uid will appear in userspace in
si_addr (on a byte by byte copy of siginfo or a field by field copy of
siginfo).  The code also verifies that for a 64bit kernel and a 32bit
userspace the 32bit pointer will fit in si_pid.

The usb code is updated to compute the sigval_t to pass to
kill_pid_usb_asyncio when the signals are registered with
the usb layer.

Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Oliver Neukum <oneukum@suse.com>
Fixes: v2.3.39
Cc: stable@vger.kernel.org
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
---
 drivers/usb/core/devio.c     | 48 ++++++++++++-------------
 include/linux/sched/signal.h |  2 +-
 kernel/signal.c              | 69 +++++++++++++++++++++++++++++++-----
 3 files changed, 86 insertions(+), 33 deletions(-)

diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index fa783531ee88..a02448105527 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -63,7 +63,7 @@ struct usb_dev_state {
 	unsigned int discsignr;
 	struct pid *disc_pid;
 	const struct cred *cred;
-	void __user *disccontext;
+	sigval_t disccontext;
 	unsigned long ifclaimed;
 	u32 disabled_bulk_eps;
 	bool privileges_dropped;
@@ -90,6 +90,7 @@ struct async {
 	unsigned int ifnum;
 	void __user *userbuffer;
 	void __user *userurb;
+	sigval_t userurb_sigval;
 	struct urb *urb;
 	struct usb_memory *usbm;
 	unsigned int mem_usage;
@@ -582,22 +583,19 @@ static void async_completed(struct urb *urb)
 {
 	struct async *as = urb->context;
 	struct usb_dev_state *ps = as->ps;
-	struct kernel_siginfo sinfo;
 	struct pid *pid = NULL;
 	const struct cred *cred = NULL;
 	unsigned long flags;
-	int signr;
+	sigval_t addr;
+	int signr, errno;
 
 	spin_lock_irqsave(&ps->lock, flags);
 	list_move_tail(&as->asynclist, &ps->async_completed);
 	as->status = urb->status;
 	signr = as->signr;
 	if (signr) {
-		clear_siginfo(&sinfo);
-		sinfo.si_signo = as->signr;
-		sinfo.si_errno = as->status;
-		sinfo.si_code = SI_ASYNCIO;
-		sinfo.si_addr = as->userurb;
+		errno = as->status;
+		addr = as->userurb_sigval;
 		pid = get_pid(as->pid);
 		cred = get_cred(as->cred);
 	}
@@ -615,7 +613,7 @@ static void async_completed(struct urb *urb)
 	spin_unlock_irqrestore(&ps->lock, flags);
 
 	if (signr) {
-		kill_pid_info_as_cred(sinfo.si_signo, &sinfo, pid, cred);
+		kill_pid_usb_asyncio(signr, errno, addr, pid, cred);
 		put_pid(pid);
 		put_cred(cred);
 	}
@@ -1427,7 +1425,7 @@ find_memory_area(struct usb_dev_state *ps, const struct usbdevfs_urb *uurb)
 
 static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb,
 			struct usbdevfs_iso_packet_desc __user *iso_frame_desc,
-			void __user *arg)
+			void __user *arg, sigval_t userurb_sigval)
 {
 	struct usbdevfs_iso_packet_desc *isopkt = NULL;
 	struct usb_host_endpoint *ep;
@@ -1727,6 +1725,7 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
 	isopkt = NULL;
 	as->ps = ps;
 	as->userurb = arg;
+	as->userurb_sigval = userurb_sigval;
 	if (as->usbm) {
 		unsigned long uurb_start = (unsigned long)uurb->buffer;
 
@@ -1801,13 +1800,17 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
 static int proc_submiturb(struct usb_dev_state *ps, void __user *arg)
 {
 	struct usbdevfs_urb uurb;
+	sigval_t userurb_sigval;
 
 	if (copy_from_user(&uurb, arg, sizeof(uurb)))
 		return -EFAULT;
 
+	memset(&userurb_sigval, 0, sizeof(userurb_sigval));
+	userurb_sigval.sival_ptr = arg;
+
 	return proc_do_submiturb(ps, &uurb,
 			(((struct usbdevfs_urb __user *)arg)->iso_frame_desc),
-			arg);
+			arg, userurb_sigval);
 }
 
 static int proc_unlinkurb(struct usb_dev_state *ps, void __user *arg)
@@ -1977,7 +1980,7 @@ static int proc_disconnectsignal_compat(struct usb_dev_state *ps, void __user *a
 	if (copy_from_user(&ds, arg, sizeof(ds)))
 		return -EFAULT;
 	ps->discsignr = ds.signr;
-	ps->disccontext = compat_ptr(ds.context);
+	ps->disccontext.sival_int = ds.context;
 	return 0;
 }
 
@@ -2005,13 +2008,17 @@ static int get_urb32(struct usbdevfs_urb *kurb,
 static int proc_submiturb_compat(struct usb_dev_state *ps, void __user *arg)
 {
 	struct usbdevfs_urb uurb;
+	sigval_t userurb_sigval;
 
 	if (get_urb32(&uurb, (struct usbdevfs_urb32 __user *)arg))
 		return -EFAULT;
 
+	memset(&userurb_sigval, 0, sizeof(userurb_sigval));
+	userurb_sigval.sival_int = ptr_to_compat(arg);
+
 	return proc_do_submiturb(ps, &uurb,
 			((struct usbdevfs_urb32 __user *)arg)->iso_frame_desc,
-			arg);
+			arg, userurb_sigval);
 }
 
 static int processcompl_compat(struct async *as, void __user * __user *arg)
@@ -2092,7 +2099,7 @@ static int proc_disconnectsignal(struct usb_dev_state *ps, void __user *arg)
 	if (copy_from_user(&ds, arg, sizeof(ds)))
 		return -EFAULT;
 	ps->discsignr = ds.signr;
-	ps->disccontext = ds.context;
+	ps->disccontext.sival_ptr = ds.context;
 	return 0;
 }
 
@@ -2614,22 +2621,15 @@ const struct file_operations usbdev_file_operations = {
 static void usbdev_remove(struct usb_device *udev)
 {
 	struct usb_dev_state *ps;
-	struct kernel_siginfo sinfo;
 
 	while (!list_empty(&udev->filelist)) {
 		ps = list_entry(udev->filelist.next, struct usb_dev_state, list);
 		destroy_all_async(ps);
 		wake_up_all(&ps->wait);
 		list_del_init(&ps->list);
-		if (ps->discsignr) {
-			clear_siginfo(&sinfo);
-			sinfo.si_signo = ps->discsignr;
-			sinfo.si_errno = EPIPE;
-			sinfo.si_code = SI_ASYNCIO;
-			sinfo.si_addr = ps->disccontext;
-			kill_pid_info_as_cred(ps->discsignr, &sinfo,
-					ps->disc_pid, ps->cred);
-		}
+		if (ps->discsignr)
+			kill_pid_usb_asyncio(ps->discsignr, EPIPE, ps->disccontext,
+					     ps->disc_pid, ps->cred);
 	}
 }
 
diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index e412c092c1e8..c735113f7d93 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -328,7 +328,7 @@ extern void force_sigsegv(int sig, struct task_struct *p);
 extern int force_sig_info(int, struct kernel_siginfo *, struct task_struct *);
 extern int __kill_pgrp_info(int sig, struct kernel_siginfo *info, struct pid *pgrp);
 extern int kill_pid_info(int sig, struct kernel_siginfo *info, struct pid *pid);
-extern int kill_pid_info_as_cred(int, struct kernel_siginfo *, struct pid *,
+extern int kill_pid_usb_asyncio(int sig, int errno, sigval_t addr, struct pid *,
 				const struct cred *);
 extern int kill_pgrp(struct pid *pid, int sig, int priv);
 extern int kill_pid(struct pid *pid, int sig, int priv);
diff --git a/kernel/signal.c b/kernel/signal.c
index 227ba170298e..101820939baf 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1436,13 +1436,44 @@ static inline bool kill_as_cred_perm(const struct cred *cred,
 	       uid_eq(cred->uid, pcred->uid);
 }
 
-/* like kill_pid_info(), but doesn't use uid/euid of "current" */
-int kill_pid_info_as_cred(int sig, struct kernel_siginfo *info, struct pid *pid,
-			 const struct cred *cred)
+/*
+ * The usb asyncio usage of siginfo is wrong.  The glibc support
+ * for asyncio which uses SI_ASYNCIO assumes the layout is SIL_RT.
+ * AKA after the generic fields:
+ *	kernel_pid_t	si_pid;
+ *	kernel_uid32_t	si_uid;
+ *	sigval_t	si_value;
+ *
+ * Unfortunately when usb generates SI_ASYNCIO it assumes the layout
+ * after the generic fields is:
+ *	void __user 	*si_addr;
+ *
+ * This is a practical problem when there is a 64bit big endian kernel
+ * and a 32bit userspace.  As the 32bit address will encoded in the low
+ * 32bits of the pointer.  Those low 32bits will be stored at higher
+ * address than appear in a 32 bit pointer.  So userspace will not
+ * see the address it was expecting for it's completions.
+ *
+ * There is nothing in the encoding that can allow
+ * copy_siginfo_to_user32 to detect this confusion of formats, so
+ * handle this by requiring the caller of kill_pid_usb_asyncio to
+ * notice when this situration takes place and to store the 32bit
+ * pointer in sival_int, instead of sival_addr of the sigval_t addr
+ * parameter.
+ */
+int kill_pid_usb_asyncio(int sig, int errno, sigval_t addr,
+			 struct pid *pid, const struct cred *cred)
 {
-	int ret = -EINVAL;
+	struct kernel_siginfo info;
 	struct task_struct *p;
 	unsigned long flags;
+	int ret = -EINVAL;
+
+	clear_siginfo(&info);
+	info.si_signo = sig;
+	info.si_errno = errno;
+	info.si_code = SI_ASYNCIO;
+	*((sigval_t *)&info.si_pid) = addr;
 
 	if (!valid_signal(sig))
 		return ret;
@@ -1453,17 +1484,17 @@ int kill_pid_info_as_cred(int sig, struct kernel_siginfo *info, struct pid *pid,
 		ret = -ESRCH;
 		goto out_unlock;
 	}
-	if (si_fromuser(info) && !kill_as_cred_perm(cred, p)) {
+	if (!kill_as_cred_perm(cred, p)) {
 		ret = -EPERM;
 		goto out_unlock;
 	}
-	ret = security_task_kill(p, info, sig, cred);
+	ret = security_task_kill(p, &info, sig, cred);
 	if (ret)
 		goto out_unlock;
 
 	if (sig) {
 		if (lock_task_sighand(p, &flags)) {
-			ret = __send_signal(sig, info, p, PIDTYPE_TGID, 0);
+			ret = __send_signal(sig, &info, p, PIDTYPE_TGID, 0);
 			unlock_task_sighand(p, &flags);
 		} else
 			ret = -ESRCH;
@@ -1472,7 +1503,7 @@ int kill_pid_info_as_cred(int sig, struct kernel_siginfo *info, struct pid *pid,
 	rcu_read_unlock();
 	return ret;
 }
-EXPORT_SYMBOL_GPL(kill_pid_info_as_cred);
+EXPORT_SYMBOL_GPL(kill_pid_usb_asyncio);
 
 /*
  * kill_something_info() interprets pid in interesting ways just like kill(2).
@@ -4408,6 +4439,28 @@ static inline void siginfo_buildtime_checks(void)
 	CHECK_OFFSET(si_syscall);
 	CHECK_OFFSET(si_arch);
 #undef CHECK_OFFSET
+
+	/* usb asyncio */
+	BUILD_BUG_ON(offsetof(struct siginfo, si_pid) !=
+		     offsetof(struct siginfo, si_addr));
+	if (sizeof(int) == sizeof(void __user *)) {
+		BUILD_BUG_ON(sizeof_field(struct siginfo, si_pid) !=
+			     sizeof(void __user *));
+	} else {
+		BUILD_BUG_ON((sizeof_field(struct siginfo, si_pid) +
+			      sizeof_field(struct siginfo, si_uid)) !=
+			     sizeof(void __user *));
+		BUILD_BUG_ON(offsetofend(struct siginfo, si_pid) !=
+			     offsetof(struct siginfo, si_uid));
+	}
+#ifdef CONFIG_COMPAT
+	BUILD_BUG_ON(offsetof(struct compat_siginfo, si_pid) !=
+		     offsetof(struct compat_siginfo, si_addr));
+	BUILD_BUG_ON(sizeof_field(struct compat_siginfo, si_pid) !=
+		     sizeof(compat_uptr_t));
+	BUILD_BUG_ON(sizeof_field(struct compat_siginfo, si_pid) !=
+		     sizeof_field(struct siginfo, si_pid));
+#endif
 }
 
 void __init signals_init(void)
-- 
2.17.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [CFT][PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-18  1:38   ` Eric W. Biederman
@ 2019-05-18 15:20     ` Alan Stern
  2019-05-18 15:25       ` Eric W. Biederman
  2019-05-21 12:40       ` [PATCH] " Eric W. Biederman
  0 siblings, 2 replies; 14+ messages in thread
From: Alan Stern @ 2019-05-18 15:20 UTC (permalink / raw)
  To: Eric W. Biederman
  Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

On Fri, 17 May 2019, Eric W. Biederman wrote:

> Wow I got a little distracted but now I am back to this.
> 
> Using your test program I was able to test the basics of this.
> 
> I found one bug in my patch where I was missing a memset.  So I have
> corrected that, and reorganized the patch a little bit.
> 
> I have not figured out how to trigger a usb disconnect so I have not
> tested that.

Heh.  Assuming the device file you tell the test program to use 
corresponds to an actual USB device, you can trigger a disconnect by 
literally unplugging the USB cable.  (Add a 10-second delay to the 
program to give yourself enough time.)

> The big thing I have not been able to test is running a 64bit big-endian
> kernel with a 32bit user space.  My modified version of your test
> program should report "Bad" without my patch, and should report "Good"
> with it.
> 
> Is there any chance you can test that configuration?  I could not figure
> out how to get a 64bit big-endian system running in qemu, and I don't
> have the necessary hardware so I was not able to test that at all.  As
> that is the actual bug I am still hoping someone can test it.

Unfortunately, I don't have any big-endian systems either.

Alan Stern


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [CFT][PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-18 15:20     ` Alan Stern
@ 2019-05-18 15:25       ` Eric W. Biederman
  2019-05-21 12:40       ` [PATCH] " Eric W. Biederman
  1 sibling, 0 replies; 14+ messages in thread
From: Eric W. Biederman @ 2019-05-18 15:25 UTC (permalink / raw)
  To: Alan Stern; +Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

Alan Stern <stern@rowland.harvard.edu> writes:

> On Fri, 17 May 2019, Eric W. Biederman wrote:
>
>> Wow I got a little distracted but now I am back to this.
>> 
>> Using your test program I was able to test the basics of this.
>> 
>> I found one bug in my patch where I was missing a memset.  So I have
>> corrected that, and reorganized the patch a little bit.
>> 
>> I have not figured out how to trigger a usb disconnect so I have not
>> tested that.
>
> Heh.  Assuming the device file you tell the test program to use 
> corresponds to an actual USB device, you can trigger a disconnect by 
> literally unplugging the USB cable.  (Add a 10-second delay to the 
> program to give yourself enough time.)

I have just been running this in qemu.  But yes.  I suppose the easy
way would be to print a message asking the usb device to be unplugged
and then just wait for the signal.  I might try that.

>> The big thing I have not been able to test is running a 64bit big-endian
>> kernel with a 32bit user space.  My modified version of your test
>> program should report "Bad" without my patch, and should report "Good"
>> with it.
>> 
>> Is there any chance you can test that configuration?  I could not figure
>> out how to get a 64bit big-endian system running in qemu, and I don't
>> have the necessary hardware so I was not able to test that at all.  As
>> that is the actual bug I am still hoping someone can test it.
>
> Unfortunately, I don't have any big-endian systems either.

That probably explains why the breakage in big-endian was never noticed.
I am starting to wonder if anyone is actually doing big-endian for new
systems anymore.

Eric

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-18 15:20     ` Alan Stern
  2019-05-18 15:25       ` Eric W. Biederman
@ 2019-05-21 12:40       ` Eric W. Biederman
  2019-05-21 14:02         ` Alan Stern
                           ` (2 more replies)
  1 sibling, 3 replies; 14+ messages in thread
From: Eric W. Biederman @ 2019-05-21 12:40 UTC (permalink / raw)
  To: Alan Stern; +Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel


The usb support for asyncio encoded one of it's values in the wrong
field.  It should have used si_value but instead used si_addr which is
not present in the _rt union member of struct siginfo.

The practical result of this is that on a 64bit big endian kernel
when delivering a signal to a 32bit process the si_addr field
is set to NULL, instead of the expected pointer value.

This issue can not be fixed in copy_siginfo_to_user32 as the usb
usage of the the _sigfault (aka si_addr) member of the siginfo
union when SI_ASYNCIO is set is incompatible with the POSIX and
glibc usage of the _rt member of the siginfo union.

Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
dedicated function for this one specific case.  There are no other
users of kill_pid_info_as_cred so this specialization should have no
impact on the amount of code in the kernel.  Have kill_pid_usb_asyncio
take instead of a siginfo_t which is difficult and error prone, 3
arguments, a signal number, an errno value, and an address enconded as
a sigval_t.  The encoding of the address as a sigval_t allows the
code that reads the userspace request for a signal to handle this
compat issue along with all of the other compat issues.

Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
the pointer value at the in si_pid (instead of si_addr).  That is the
code now verifies that si_pid and si_addr always occur at the same
location.  Further the code veries that for native structures a value
placed in si_pid and spilling into si_uid will appear in userspace in
si_addr (on a byte by byte copy of siginfo or a field by field copy of
siginfo).  The code also verifies that for a 64bit kernel and a 32bit
userspace the 32bit pointer will fit in si_pid.

I have used the usbsig.c program below written by Alan Stern and
slightly tweaked by me to run on a big endian machine to verify the
issue exists (on sparc64) and to confirm the patch below fixes the issue.

/* usbsig.c -- test USB async signal delivery */

static struct usbdevfs_urb urb;
static struct usbdevfs_disconnectsignal ds;
static volatile sig_atomic_t done = 0;

void urb_handler(int sig, siginfo_t *info , void *ucontext)
{
	printf("Got signal %d, signo %d errno %d code %d addr: %p urb: %p\n",
	       sig, info->si_signo, info->si_errno, info->si_code,
	       info->si_addr, &urb);

	printf("%s\n", (info->si_addr == &urb) ? "Good" : "Bad");
}

void ds_handler(int sig, siginfo_t *info , void *ucontext)
{
	printf("Got signal %d, signo %d errno %d code %d addr: %p ds: %p\n",
	       sig, info->si_signo, info->si_errno, info->si_code,
	       info->si_addr, &ds);

	printf("%s\n", (info->si_addr == &ds) ? "Good" : "Bad");
	done = 1;
}

int main(int argc, char **argv)
{
	char *devfilename;
	int fd;
	int rc;
	struct sigaction act;
	struct usb_ctrlrequest *req;
	void *ptr;
	char buf[80];

	if (argc != 2) {
		fprintf(stderr, "Usage: usbsig device-file-name\n");
		return 1;
	}

	devfilename = argv[1];
	fd = open(devfilename, O_RDWR);
	if (fd == -1) {
		perror("Error opening device file");
		return 1;
	}

	act.sa_sigaction = urb_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;

	rc = sigaction(SIGUSR1, &act, NULL);
	if (rc == -1) {
		perror("Error in sigaction");
		return 1;
	}

	act.sa_sigaction = ds_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;

	rc = sigaction(SIGUSR2, &act, NULL);
	if (rc == -1) {
		perror("Error in sigaction");
		return 1;
	}

	memset(&urb, 0, sizeof(urb));
	urb.type = USBDEVFS_URB_TYPE_CONTROL;
	urb.endpoint = USB_DIR_IN | 0;
	urb.buffer = buf;
	urb.buffer_length = sizeof(buf);
	urb.signr = SIGUSR1;

	req = (struct usb_ctrlrequest *) buf;
	req->bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
	req->bRequest = USB_REQ_GET_DESCRIPTOR;
	req->wValue = htole16(USB_DT_DEVICE << 8);
	req->wIndex = htole16(0);
	req->wLength = htole16(sizeof(buf) - sizeof(*req));

	rc = ioctl(fd, USBDEVFS_SUBMITURB, &urb);
	if (rc == -1) {
		perror("Error in SUBMITURB ioctl");
		return 1;
	}

	rc = ioctl(fd, USBDEVFS_REAPURB, &ptr);
	if (rc == -1) {
		perror("Error in REAPURB ioctl");
		return 1;
	}

	memset(&ds, 0, sizeof(ds));
	ds.signr = SIGUSR2;
	ds.context = &ds;
	rc = ioctl(fd, USBDEVFS_DISCSIGNAL, &ds);
	if (rc == -1) {
		perror("Error in DISCSIGNAL ioctl");
		return 1;
	}

	printf("Waiting for usb disconnect\n");
	while (!done) {
		sleep(1);
	}

	close(fd);
	return 0;
}

Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Oliver Neukum <oneukum@suse.com>
Fixes: v2.3.39
Cc: stable@vger.kernel.org
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
---

I managed to wrestle a sparc64 qemu to the ground so I could verify this
bug exists and the patch below fixes it.

Can I get an Ack from the usb side of things?

I intend to merge this through my siginfo tree unless someone objects.

Thank you,
Eric Biederman

 drivers/usb/core/devio.c     | 48 ++++++++++++-------------
 include/linux/sched/signal.h |  2 +-
 kernel/signal.c              | 69 +++++++++++++++++++++++++++++++-----
 3 files changed, 86 insertions(+), 33 deletions(-)

diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index fa783531ee88..a02448105527 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -63,7 +63,7 @@ struct usb_dev_state {
 	unsigned int discsignr;
 	struct pid *disc_pid;
 	const struct cred *cred;
-	void __user *disccontext;
+	sigval_t disccontext;
 	unsigned long ifclaimed;
 	u32 disabled_bulk_eps;
 	bool privileges_dropped;
@@ -90,6 +90,7 @@ struct async {
 	unsigned int ifnum;
 	void __user *userbuffer;
 	void __user *userurb;
+	sigval_t userurb_sigval;
 	struct urb *urb;
 	struct usb_memory *usbm;
 	unsigned int mem_usage;
@@ -582,22 +583,19 @@ static void async_completed(struct urb *urb)
 {
 	struct async *as = urb->context;
 	struct usb_dev_state *ps = as->ps;
-	struct kernel_siginfo sinfo;
 	struct pid *pid = NULL;
 	const struct cred *cred = NULL;
 	unsigned long flags;
-	int signr;
+	sigval_t addr;
+	int signr, errno;
 
 	spin_lock_irqsave(&ps->lock, flags);
 	list_move_tail(&as->asynclist, &ps->async_completed);
 	as->status = urb->status;
 	signr = as->signr;
 	if (signr) {
-		clear_siginfo(&sinfo);
-		sinfo.si_signo = as->signr;
-		sinfo.si_errno = as->status;
-		sinfo.si_code = SI_ASYNCIO;
-		sinfo.si_addr = as->userurb;
+		errno = as->status;
+		addr = as->userurb_sigval;
 		pid = get_pid(as->pid);
 		cred = get_cred(as->cred);
 	}
@@ -615,7 +613,7 @@ static void async_completed(struct urb *urb)
 	spin_unlock_irqrestore(&ps->lock, flags);
 
 	if (signr) {
-		kill_pid_info_as_cred(sinfo.si_signo, &sinfo, pid, cred);
+		kill_pid_usb_asyncio(signr, errno, addr, pid, cred);
 		put_pid(pid);
 		put_cred(cred);
 	}
@@ -1427,7 +1425,7 @@ find_memory_area(struct usb_dev_state *ps, const struct usbdevfs_urb *uurb)
 
 static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb,
 			struct usbdevfs_iso_packet_desc __user *iso_frame_desc,
-			void __user *arg)
+			void __user *arg, sigval_t userurb_sigval)
 {
 	struct usbdevfs_iso_packet_desc *isopkt = NULL;
 	struct usb_host_endpoint *ep;
@@ -1727,6 +1725,7 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
 	isopkt = NULL;
 	as->ps = ps;
 	as->userurb = arg;
+	as->userurb_sigval = userurb_sigval;
 	if (as->usbm) {
 		unsigned long uurb_start = (unsigned long)uurb->buffer;
 
@@ -1801,13 +1800,17 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
 static int proc_submiturb(struct usb_dev_state *ps, void __user *arg)
 {
 	struct usbdevfs_urb uurb;
+	sigval_t userurb_sigval;
 
 	if (copy_from_user(&uurb, arg, sizeof(uurb)))
 		return -EFAULT;
 
+	memset(&userurb_sigval, 0, sizeof(userurb_sigval));
+	userurb_sigval.sival_ptr = arg;
+
 	return proc_do_submiturb(ps, &uurb,
 			(((struct usbdevfs_urb __user *)arg)->iso_frame_desc),
-			arg);
+			arg, userurb_sigval);
 }
 
 static int proc_unlinkurb(struct usb_dev_state *ps, void __user *arg)
@@ -1977,7 +1980,7 @@ static int proc_disconnectsignal_compat(struct usb_dev_state *ps, void __user *a
 	if (copy_from_user(&ds, arg, sizeof(ds)))
 		return -EFAULT;
 	ps->discsignr = ds.signr;
-	ps->disccontext = compat_ptr(ds.context);
+	ps->disccontext.sival_int = ds.context;
 	return 0;
 }
 
@@ -2005,13 +2008,17 @@ static int get_urb32(struct usbdevfs_urb *kurb,
 static int proc_submiturb_compat(struct usb_dev_state *ps, void __user *arg)
 {
 	struct usbdevfs_urb uurb;
+	sigval_t userurb_sigval;
 
 	if (get_urb32(&uurb, (struct usbdevfs_urb32 __user *)arg))
 		return -EFAULT;
 
+	memset(&userurb_sigval, 0, sizeof(userurb_sigval));
+	userurb_sigval.sival_int = ptr_to_compat(arg);
+
 	return proc_do_submiturb(ps, &uurb,
 			((struct usbdevfs_urb32 __user *)arg)->iso_frame_desc,
-			arg);
+			arg, userurb_sigval);
 }
 
 static int processcompl_compat(struct async *as, void __user * __user *arg)
@@ -2092,7 +2099,7 @@ static int proc_disconnectsignal(struct usb_dev_state *ps, void __user *arg)
 	if (copy_from_user(&ds, arg, sizeof(ds)))
 		return -EFAULT;
 	ps->discsignr = ds.signr;
-	ps->disccontext = ds.context;
+	ps->disccontext.sival_ptr = ds.context;
 	return 0;
 }
 
@@ -2614,22 +2621,15 @@ const struct file_operations usbdev_file_operations = {
 static void usbdev_remove(struct usb_device *udev)
 {
 	struct usb_dev_state *ps;
-	struct kernel_siginfo sinfo;
 
 	while (!list_empty(&udev->filelist)) {
 		ps = list_entry(udev->filelist.next, struct usb_dev_state, list);
 		destroy_all_async(ps);
 		wake_up_all(&ps->wait);
 		list_del_init(&ps->list);
-		if (ps->discsignr) {
-			clear_siginfo(&sinfo);
-			sinfo.si_signo = ps->discsignr;
-			sinfo.si_errno = EPIPE;
-			sinfo.si_code = SI_ASYNCIO;
-			sinfo.si_addr = ps->disccontext;
-			kill_pid_info_as_cred(ps->discsignr, &sinfo,
-					ps->disc_pid, ps->cred);
-		}
+		if (ps->discsignr)
+			kill_pid_usb_asyncio(ps->discsignr, EPIPE, ps->disccontext,
+					     ps->disc_pid, ps->cred);
 	}
 }
 
diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index 38a0f0785323..c68ca81db0a1 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -329,7 +329,7 @@ extern void force_sigsegv(int sig, struct task_struct *p);
 extern int force_sig_info(int, struct kernel_siginfo *, struct task_struct *);
 extern int __kill_pgrp_info(int sig, struct kernel_siginfo *info, struct pid *pgrp);
 extern int kill_pid_info(int sig, struct kernel_siginfo *info, struct pid *pid);
-extern int kill_pid_info_as_cred(int, struct kernel_siginfo *, struct pid *,
+extern int kill_pid_usb_asyncio(int sig, int errno, sigval_t addr, struct pid *,
 				const struct cred *);
 extern int kill_pgrp(struct pid *pid, int sig, int priv);
 extern int kill_pid(struct pid *pid, int sig, int priv);
diff --git a/kernel/signal.c b/kernel/signal.c
index a1eb44dc9ff5..18040d6bd63a 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1439,13 +1439,44 @@ static inline bool kill_as_cred_perm(const struct cred *cred,
 	       uid_eq(cred->uid, pcred->uid);
 }
 
-/* like kill_pid_info(), but doesn't use uid/euid of "current" */
-int kill_pid_info_as_cred(int sig, struct kernel_siginfo *info, struct pid *pid,
-			 const struct cred *cred)
+/*
+ * The usb asyncio usage of siginfo is wrong.  The glibc support
+ * for asyncio which uses SI_ASYNCIO assumes the layout is SIL_RT.
+ * AKA after the generic fields:
+ *	kernel_pid_t	si_pid;
+ *	kernel_uid32_t	si_uid;
+ *	sigval_t	si_value;
+ *
+ * Unfortunately when usb generates SI_ASYNCIO it assumes the layout
+ * after the generic fields is:
+ *	void __user 	*si_addr;
+ *
+ * This is a practical problem when there is a 64bit big endian kernel
+ * and a 32bit userspace.  As the 32bit address will encoded in the low
+ * 32bits of the pointer.  Those low 32bits will be stored at higher
+ * address than appear in a 32 bit pointer.  So userspace will not
+ * see the address it was expecting for it's completions.
+ *
+ * There is nothing in the encoding that can allow
+ * copy_siginfo_to_user32 to detect this confusion of formats, so
+ * handle this by requiring the caller of kill_pid_usb_asyncio to
+ * notice when this situration takes place and to store the 32bit
+ * pointer in sival_int, instead of sival_addr of the sigval_t addr
+ * parameter.
+ */
+int kill_pid_usb_asyncio(int sig, int errno, sigval_t addr,
+			 struct pid *pid, const struct cred *cred)
 {
-	int ret = -EINVAL;
+	struct kernel_siginfo info;
 	struct task_struct *p;
 	unsigned long flags;
+	int ret = -EINVAL;
+
+	clear_siginfo(&info);
+	info.si_signo = sig;
+	info.si_errno = errno;
+	info.si_code = SI_ASYNCIO;
+	*((sigval_t *)&info.si_pid) = addr;
 
 	if (!valid_signal(sig))
 		return ret;
@@ -1456,17 +1487,17 @@ int kill_pid_info_as_cred(int sig, struct kernel_siginfo *info, struct pid *pid,
 		ret = -ESRCH;
 		goto out_unlock;
 	}
-	if (si_fromuser(info) && !kill_as_cred_perm(cred, p)) {
+	if (!kill_as_cred_perm(cred, p)) {
 		ret = -EPERM;
 		goto out_unlock;
 	}
-	ret = security_task_kill(p, info, sig, cred);
+	ret = security_task_kill(p, &info, sig, cred);
 	if (ret)
 		goto out_unlock;
 
 	if (sig) {
 		if (lock_task_sighand(p, &flags)) {
-			ret = __send_signal(sig, info, p, PIDTYPE_TGID, 0);
+			ret = __send_signal(sig, &info, p, PIDTYPE_TGID, 0);
 			unlock_task_sighand(p, &flags);
 		} else
 			ret = -ESRCH;
@@ -1475,7 +1506,7 @@ int kill_pid_info_as_cred(int sig, struct kernel_siginfo *info, struct pid *pid,
 	rcu_read_unlock();
 	return ret;
 }
-EXPORT_SYMBOL_GPL(kill_pid_info_as_cred);
+EXPORT_SYMBOL_GPL(kill_pid_usb_asyncio);
 
 /*
  * kill_something_info() interprets pid in interesting ways just like kill(2).
@@ -4474,6 +4505,28 @@ static inline void siginfo_buildtime_checks(void)
 	CHECK_OFFSET(si_syscall);
 	CHECK_OFFSET(si_arch);
 #undef CHECK_OFFSET
+
+	/* usb asyncio */
+	BUILD_BUG_ON(offsetof(struct siginfo, si_pid) !=
+		     offsetof(struct siginfo, si_addr));
+	if (sizeof(int) == sizeof(void __user *)) {
+		BUILD_BUG_ON(sizeof_field(struct siginfo, si_pid) !=
+			     sizeof(void __user *));
+	} else {
+		BUILD_BUG_ON((sizeof_field(struct siginfo, si_pid) +
+			      sizeof_field(struct siginfo, si_uid)) !=
+			     sizeof(void __user *));
+		BUILD_BUG_ON(offsetofend(struct siginfo, si_pid) !=
+			     offsetof(struct siginfo, si_uid));
+	}
+#ifdef CONFIG_COMPAT
+	BUILD_BUG_ON(offsetof(struct compat_siginfo, si_pid) !=
+		     offsetof(struct compat_siginfo, si_addr));
+	BUILD_BUG_ON(sizeof_field(struct compat_siginfo, si_pid) !=
+		     sizeof(compat_uptr_t));
+	BUILD_BUG_ON(sizeof_field(struct compat_siginfo, si_pid) !=
+		     sizeof_field(struct siginfo, si_pid));
+#endif
 }
 
 void __init signals_init(void)
-- 
2.17.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-21 12:40       ` [PATCH] " Eric W. Biederman
@ 2019-05-21 14:02         ` Alan Stern
  2019-05-21 14:47           ` Eric W. Biederman
  2019-05-22 19:02         ` Alan Stern
  2019-06-06 14:27         ` Greg Kroah-Hartman
  2 siblings, 1 reply; 14+ messages in thread
From: Alan Stern @ 2019-05-21 14:02 UTC (permalink / raw)
  To: Eric W. Biederman
  Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

On Tue, 21 May 2019, Eric W. Biederman wrote:

> The usb support for asyncio encoded one of it's values in the wrong
> field.  It should have used si_value but instead used si_addr which is
> not present in the _rt union member of struct siginfo.
> 
> The practical result of this is that on a 64bit big endian kernel
> when delivering a signal to a 32bit process the si_addr field
> is set to NULL, instead of the expected pointer value.
> 
> This issue can not be fixed in copy_siginfo_to_user32 as the usb
> usage of the the _sigfault (aka si_addr) member of the siginfo
> union when SI_ASYNCIO is set is incompatible with the POSIX and
> glibc usage of the _rt member of the siginfo union.
> 
> Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
> dedicated function for this one specific case.  There are no other
> users of kill_pid_info_as_cred so this specialization should have no
> impact on the amount of code in the kernel.  Have kill_pid_usb_asyncio
> take instead of a siginfo_t which is difficult and error prone, 3
> arguments, a signal number, an errno value, and an address enconded as
> a sigval_t.  The encoding of the address as a sigval_t allows the
> code that reads the userspace request for a signal to handle this
> compat issue along with all of the other compat issues.
> 
> Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
> the pointer value at the in si_pid (instead of si_addr).  That is the
> code now verifies that si_pid and si_addr always occur at the same
> location.  Further the code veries that for native structures a value
> placed in si_pid and spilling into si_uid will appear in userspace in
> si_addr (on a byte by byte copy of siginfo or a field by field copy of
> siginfo).  The code also verifies that for a 64bit kernel and a 32bit
> userspace the 32bit pointer will fit in si_pid.
> 
> I have used the usbsig.c program below written by Alan Stern and
> slightly tweaked by me to run on a big endian machine to verify the
> issue exists (on sparc64) and to confirm the patch below fixes the issue.
> 
> /* usbsig.c -- test USB async signal delivery */
> 
> static struct usbdevfs_urb urb;
> static struct usbdevfs_disconnectsignal ds;
> static volatile sig_atomic_t done = 0;
> 
> void urb_handler(int sig, siginfo_t *info , void *ucontext)
> {
> 	printf("Got signal %d, signo %d errno %d code %d addr: %p urb: %p\n",
> 	       sig, info->si_signo, info->si_errno, info->si_code,
> 	       info->si_addr, &urb);
> 
> 	printf("%s\n", (info->si_addr == &urb) ? "Good" : "Bad");
> }
> 
> void ds_handler(int sig, siginfo_t *info , void *ucontext)
> {
> 	printf("Got signal %d, signo %d errno %d code %d addr: %p ds: %p\n",
> 	       sig, info->si_signo, info->si_errno, info->si_code,
> 	       info->si_addr, &ds);
> 
> 	printf("%s\n", (info->si_addr == &ds) ? "Good" : "Bad");
> 	done = 1;
> }
> 
> int main(int argc, char **argv)
> {
> 	char *devfilename;
> 	int fd;
> 	int rc;
> 	struct sigaction act;
> 	struct usb_ctrlrequest *req;
> 	void *ptr;
> 	char buf[80];
> 
> 	if (argc != 2) {
> 		fprintf(stderr, "Usage: usbsig device-file-name\n");
> 		return 1;
> 	}
> 
> 	devfilename = argv[1];
> 	fd = open(devfilename, O_RDWR);
> 	if (fd == -1) {
> 		perror("Error opening device file");
> 		return 1;
> 	}
> 
> 	act.sa_sigaction = urb_handler;
> 	sigemptyset(&act.sa_mask);
> 	act.sa_flags = SA_SIGINFO;
> 
> 	rc = sigaction(SIGUSR1, &act, NULL);
> 	if (rc == -1) {
> 		perror("Error in sigaction");
> 		return 1;
> 	}
> 
> 	act.sa_sigaction = ds_handler;
> 	sigemptyset(&act.sa_mask);
> 	act.sa_flags = SA_SIGINFO;
> 
> 	rc = sigaction(SIGUSR2, &act, NULL);
> 	if (rc == -1) {
> 		perror("Error in sigaction");
> 		return 1;
> 	}
> 
> 	memset(&urb, 0, sizeof(urb));
> 	urb.type = USBDEVFS_URB_TYPE_CONTROL;
> 	urb.endpoint = USB_DIR_IN | 0;
> 	urb.buffer = buf;
> 	urb.buffer_length = sizeof(buf);
> 	urb.signr = SIGUSR1;
> 
> 	req = (struct usb_ctrlrequest *) buf;
> 	req->bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
> 	req->bRequest = USB_REQ_GET_DESCRIPTOR;
> 	req->wValue = htole16(USB_DT_DEVICE << 8);
> 	req->wIndex = htole16(0);
> 	req->wLength = htole16(sizeof(buf) - sizeof(*req));

In fact, these values are supposed to be in host-endian order, not 
necessarily little-endian.  The USB core converts them if necessary.

> 	rc = ioctl(fd, USBDEVFS_SUBMITURB, &urb);
> 	if (rc == -1) {
> 		perror("Error in SUBMITURB ioctl");
> 		return 1;
> 	}
> 
> 	rc = ioctl(fd, USBDEVFS_REAPURB, &ptr);
> 	if (rc == -1) {
> 		perror("Error in REAPURB ioctl");
> 		return 1;
> 	}
> 
> 	memset(&ds, 0, sizeof(ds));
> 	ds.signr = SIGUSR2;
> 	ds.context = &ds;
> 	rc = ioctl(fd, USBDEVFS_DISCSIGNAL, &ds);
> 	if (rc == -1) {
> 		perror("Error in DISCSIGNAL ioctl");
> 		return 1;
> 	}
> 
> 	printf("Waiting for usb disconnect\n");
> 	while (!done) {
> 		sleep(1);
> 	}
> 
> 	close(fd);
> 	return 0;
> }
> 
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: linux-usb@vger.kernel.org
> Cc: Alan Stern <stern@rowland.harvard.edu>
> Cc: Oliver Neukum <oneukum@suse.com>
> Fixes: v2.3.39
> Cc: stable@vger.kernel.org
> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
> ---
> 
> I managed to wrestle a sparc64 qemu to the ground so I could verify this
> bug exists and the patch below fixes it.
> 
> Can I get an Ack from the usb side of things?

Give me some time to review the description and the changes.

Alan Stern


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-21 14:02         ` Alan Stern
@ 2019-05-21 14:47           ` Eric W. Biederman
  2019-05-21 15:30             ` Alan Stern
  0 siblings, 1 reply; 14+ messages in thread
From: Eric W. Biederman @ 2019-05-21 14:47 UTC (permalink / raw)
  To: Alan Stern; +Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

Alan Stern <stern@rowland.harvard.edu> writes:

> On Tue, 21 May 2019, Eric W. Biederman wrote:
>
>> The usb support for asyncio encoded one of it's values in the wrong
>> field.  It should have used si_value but instead used si_addr which is
>> not present in the _rt union member of struct siginfo.
>> 
>> The practical result of this is that on a 64bit big endian kernel
>> when delivering a signal to a 32bit process the si_addr field
>> is set to NULL, instead of the expected pointer value.
>> 
>> This issue can not be fixed in copy_siginfo_to_user32 as the usb
>> usage of the the _sigfault (aka si_addr) member of the siginfo
>> union when SI_ASYNCIO is set is incompatible with the POSIX and
>> glibc usage of the _rt member of the siginfo union.
>> 
>> Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
>> dedicated function for this one specific case.  There are no other
>> users of kill_pid_info_as_cred so this specialization should have no
>> impact on the amount of code in the kernel.  Have kill_pid_usb_asyncio
>> take instead of a siginfo_t which is difficult and error prone, 3
>> arguments, a signal number, an errno value, and an address enconded as
>> a sigval_t.  The encoding of the address as a sigval_t allows the
>> code that reads the userspace request for a signal to handle this
>> compat issue along with all of the other compat issues.
>> 
>> Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
>> the pointer value at the in si_pid (instead of si_addr).  That is the
>> code now verifies that si_pid and si_addr always occur at the same
>> location.  Further the code veries that for native structures a value
>> placed in si_pid and spilling into si_uid will appear in userspace in
>> si_addr (on a byte by byte copy of siginfo or a field by field copy of
>> siginfo).  The code also verifies that for a 64bit kernel and a 32bit
>> userspace the 32bit pointer will fit in si_pid.
>> 
>> I have used the usbsig.c program below written by Alan Stern and
>> slightly tweaked by me to run on a big endian machine to verify the
>> issue exists (on sparc64) and to confirm the patch below fixes the issue.
>> 
>> /* usbsig.c -- test USB async signal delivery */

Sigh git commit ate the includes...

>> static struct usbdevfs_urb urb;
>> static struct usbdevfs_disconnectsignal ds;
>> static volatile sig_atomic_t done = 0;
>> 
>> void urb_handler(int sig, siginfo_t *info , void *ucontext)
>> {
>> 	printf("Got signal %d, signo %d errno %d code %d addr: %p urb: %p\n",
>> 	       sig, info->si_signo, info->si_errno, info->si_code,
>> 	       info->si_addr, &urb);
>> 
>> 	printf("%s\n", (info->si_addr == &urb) ? "Good" : "Bad");
>> }
>> 
>> void ds_handler(int sig, siginfo_t *info , void *ucontext)
>> {
>> 	printf("Got signal %d, signo %d errno %d code %d addr: %p ds: %p\n",
>> 	       sig, info->si_signo, info->si_errno, info->si_code,
>> 	       info->si_addr, &ds);
>> 
>> 	printf("%s\n", (info->si_addr == &ds) ? "Good" : "Bad");
>> 	done = 1;
>> }
>> 
>> int main(int argc, char **argv)
>> {
>> 	char *devfilename;
>> 	int fd;
>> 	int rc;
>> 	struct sigaction act;
>> 	struct usb_ctrlrequest *req;
>> 	void *ptr;
>> 	char buf[80];
>> 
>> 	if (argc != 2) {
>> 		fprintf(stderr, "Usage: usbsig device-file-name\n");
>> 		return 1;
>> 	}
>> 
>> 	devfilename = argv[1];
>> 	fd = open(devfilename, O_RDWR);
>> 	if (fd == -1) {
>> 		perror("Error opening device file");
>> 		return 1;
>> 	}
>> 
>> 	act.sa_sigaction = urb_handler;
>> 	sigemptyset(&act.sa_mask);
>> 	act.sa_flags = SA_SIGINFO;
>> 
>> 	rc = sigaction(SIGUSR1, &act, NULL);
>> 	if (rc == -1) {
>> 		perror("Error in sigaction");
>> 		return 1;
>> 	}
>> 
>> 	act.sa_sigaction = ds_handler;
>> 	sigemptyset(&act.sa_mask);
>> 	act.sa_flags = SA_SIGINFO;
>> 
>> 	rc = sigaction(SIGUSR2, &act, NULL);
>> 	if (rc == -1) {
>> 		perror("Error in sigaction");
>> 		return 1;
>> 	}
>> 
>> 	memset(&urb, 0, sizeof(urb));
>> 	urb.type = USBDEVFS_URB_TYPE_CONTROL;
>> 	urb.endpoint = USB_DIR_IN | 0;
>> 	urb.buffer = buf;
>> 	urb.buffer_length = sizeof(buf);
>> 	urb.signr = SIGUSR1;
>> 
>> 	req = (struct usb_ctrlrequest *) buf;
>> 	req->bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
>> 	req->bRequest = USB_REQ_GET_DESCRIPTOR;
>> 	req->wValue = htole16(USB_DT_DEVICE << 8);
>> 	req->wIndex = htole16(0);
>> 	req->wLength = htole16(sizeof(buf) - sizeof(*req));
>
> In fact, these values are supposed to be in host-endian order, not 
> necessarily little-endian.  The USB core converts them if necessary.

Please look again.  In include/uapi/linux/ch9.h those fields are
explicitly defined as little endian and the code in devio.c for
USBDEVFS_URB_TYPE_CONTROL treats them as little endian.   Perhaps there
is a mismatch here but I haven't seen it and I needed this change to get
the code to work on big endian.

>> 	rc = ioctl(fd, USBDEVFS_SUBMITURB, &urb);
>> 	if (rc == -1) {
>> 		perror("Error in SUBMITURB ioctl");
>> 		return 1;
>> 	}
>> 
>> 	rc = ioctl(fd, USBDEVFS_REAPURB, &ptr);
>> 	if (rc == -1) {
>> 		perror("Error in REAPURB ioctl");
>> 		return 1;
>> 	}
>> 
>> 	memset(&ds, 0, sizeof(ds));
>> 	ds.signr = SIGUSR2;
>> 	ds.context = &ds;
>> 	rc = ioctl(fd, USBDEVFS_DISCSIGNAL, &ds);
>> 	if (rc == -1) {
>> 		perror("Error in DISCSIGNAL ioctl");
>> 		return 1;
>> 	}
>> 
>> 	printf("Waiting for usb disconnect\n");
>> 	while (!done) {
>> 		sleep(1);
>> 	}
>> 
>> 	close(fd);
>> 	return 0;
>> }
>> 
>> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
>> Cc: linux-usb@vger.kernel.org
>> Cc: Alan Stern <stern@rowland.harvard.edu>
>> Cc: Oliver Neukum <oneukum@suse.com>
>> Fixes: v2.3.39
>> Cc: stable@vger.kernel.org
>> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
>> ---
>> 
>> I managed to wrestle a sparc64 qemu to the ground so I could verify this
>> bug exists and the patch below fixes it.
>> 
>> Can I get an Ack from the usb side of things?
>
> Give me some time to review the description and the changes.

Please, it always helps when more people understand these things.

Eric

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-21 14:47           ` Eric W. Biederman
@ 2019-05-21 15:30             ` Alan Stern
  0 siblings, 0 replies; 14+ messages in thread
From: Alan Stern @ 2019-05-21 15:30 UTC (permalink / raw)
  To: Eric W. Biederman
  Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

On Tue, 21 May 2019, Eric W. Biederman wrote:

> Alan Stern <stern@rowland.harvard.edu> writes:

> >> 	req = (struct usb_ctrlrequest *) buf;
> >> 	req->bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
> >> 	req->bRequest = USB_REQ_GET_DESCRIPTOR;
> >> 	req->wValue = htole16(USB_DT_DEVICE << 8);
> >> 	req->wIndex = htole16(0);
> >> 	req->wLength = htole16(sizeof(buf) - sizeof(*req));
> >
> > In fact, these values are supposed to be in host-endian order, not 
> > necessarily little-endian.  The USB core converts them if necessary.
> 
> Please look again.  In include/uapi/linux/ch9.h those fields are
> explicitly defined as little endian and the code in devio.c for
> USBDEVFS_URB_TYPE_CONTROL treats them as little endian.   Perhaps there
> is a mismatch here but I haven't seen it and I needed this change to get
> the code to work on big endian.

Oops -- you're right.  I was thinking of the USBDEVFS_CONTROL ioctl.  
Sorry for the mistake.

Alan Stern


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-21 12:40       ` [PATCH] " Eric W. Biederman
  2019-05-21 14:02         ` Alan Stern
@ 2019-05-22 19:02         ` Alan Stern
  2019-05-22 21:50           ` Eric W. Biederman
  2019-06-06 14:27         ` Greg Kroah-Hartman
  2 siblings, 1 reply; 14+ messages in thread
From: Alan Stern @ 2019-05-22 19:02 UTC (permalink / raw)
  To: Eric W. Biederman
  Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

On Tue, 21 May 2019, Eric W. Biederman wrote:

> The usb support for asyncio encoded one of it's values in the wrong
> field.  It should have used si_value but instead used si_addr which is
> not present in the _rt union member of struct siginfo.
> 
> The practical result of this is that on a 64bit big endian kernel
> when delivering a signal to a 32bit process the si_addr field
> is set to NULL, instead of the expected pointer value.
> 
> This issue can not be fixed in copy_siginfo_to_user32 as the usb
> usage of the the _sigfault (aka si_addr) member of the siginfo
> union when SI_ASYNCIO is set is incompatible with the POSIX and
> glibc usage of the _rt member of the siginfo union.
> 
> Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
> dedicated function for this one specific case.  There are no other
> users of kill_pid_info_as_cred so this specialization should have no
> impact on the amount of code in the kernel.  Have kill_pid_usb_asyncio
> take instead of a siginfo_t which is difficult and error prone, 3
> arguments, a signal number, an errno value, and an address enconded as
> a sigval_t.  The encoding of the address as a sigval_t allows the
> code that reads the userspace request for a signal to handle this
> compat issue along with all of the other compat issues.
> 
> Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
> the pointer value at the in si_pid (instead of si_addr).  That is the
> code now verifies that si_pid and si_addr always occur at the same
> location.  Further the code veries that for native structures a value
> placed in si_pid and spilling into si_uid will appear in userspace in
> si_addr (on a byte by byte copy of siginfo or a field by field copy of
> siginfo).  The code also verifies that for a 64bit kernel and a 32bit
> userspace the 32bit pointer will fit in si_pid.

Okay, I have gone through this.  Although I still don't really
understand the detailed issues concerning the layout of the data fields
(probably hopeless without seeing a diagram), the USB portions of the
patch look good and do what the patch description says.

Acked-by: Alan Stern <stern@rowland.harvard.edu>

Alan Stern


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-22 19:02         ` Alan Stern
@ 2019-05-22 21:50           ` Eric W. Biederman
  2019-05-23 18:12             ` Alan Stern
  0 siblings, 1 reply; 14+ messages in thread
From: Eric W. Biederman @ 2019-05-22 21:50 UTC (permalink / raw)
  To: Alan Stern; +Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

Alan Stern <stern@rowland.harvard.edu> writes:

> On Tue, 21 May 2019, Eric W. Biederman wrote:
>
>> The usb support for asyncio encoded one of it's values in the wrong
>> field.  It should have used si_value but instead used si_addr which is
>> not present in the _rt union member of struct siginfo.
>> 
>> The practical result of this is that on a 64bit big endian kernel
>> when delivering a signal to a 32bit process the si_addr field
>> is set to NULL, instead of the expected pointer value.
>> 
>> This issue can not be fixed in copy_siginfo_to_user32 as the usb
>> usage of the the _sigfault (aka si_addr) member of the siginfo
>> union when SI_ASYNCIO is set is incompatible with the POSIX and
>> glibc usage of the _rt member of the siginfo union.
>> 
>> Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
>> dedicated function for this one specific case.  There are no other
>> users of kill_pid_info_as_cred so this specialization should have no
>> impact on the amount of code in the kernel.  Have kill_pid_usb_asyncio
>> take instead of a siginfo_t which is difficult and error prone, 3
>> arguments, a signal number, an errno value, and an address enconded as
>> a sigval_t.  The encoding of the address as a sigval_t allows the
>> code that reads the userspace request for a signal to handle this
>> compat issue along with all of the other compat issues.
>> 
>> Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
>> the pointer value at the in si_pid (instead of si_addr).  That is the
>> code now verifies that si_pid and si_addr always occur at the same
>> location.  Further the code veries that for native structures a value
>> placed in si_pid and spilling into si_uid will appear in userspace in
>> si_addr (on a byte by byte copy of siginfo or a field by field copy of
>> siginfo).  The code also verifies that for a 64bit kernel and a 32bit
>> userspace the 32bit pointer will fit in si_pid.
>
> Okay, I have gone through this.  Although I still don't really
> understand the detailed issues concerning the layout of the data fields
> (probably hopeless without seeing a diagram), the USB portions of the
> patch look good and do what the patch description says.
>
> Acked-by: Alan Stern <stern@rowland.harvard.edu>
>
> Alan Stern

Thanks.

Perhaps this will work as a diagram.  I don't know if there is a better
way to say it in my patch description.  In struct siginfo there are 3
fields in fixed positions:

   int si_signo;
   int si_errno;
   int si_code;

After that there is a union.  The si_signo and si_code fields are
examined to see which union member is valid (see siginfo_layout).
In every other case a si_code of SI_ASYNCIO corresponds to
the the _rt union member which has the fields:

   int si_pid;
   int si_uid;
   sigval_t si_sigval;

However when usb started using SI_ASYNCIO the _sigfault union member
that (except for special exceptions) only has the field:

   void __user *si_addr;

Or in short the relevant piece of the union looks like:

         0   1  2   3    4   5   6  7
       +---+---+---+---+---+---+---+---+
       |    si_pid     |   si_uid      |
       +---+---+---+---+---+---+---+---+
       |             si_addr           | (64bit)
       +---+---+---+---+---+---+---+---+
       |     si_addr   | (32bit)
       +---+---+---+---+

Which means if siginfo is copied field by field on 32bit everything
works because si_pid and si_addr are in the same location.

Similarly if siginfo is copied field by field on 64bit everything
works because there is no padding between si_pid and si_uid. So
copying both of those fields results in the entire si_addr being
copied.

It is the compat case that gets tricky.  Half of the bits are
zero.  If those zero bits show up in bytes 4-7 and the data
shows up in bytes 0-3 (aka little endian) everything works.
If those zero bits show in in bytes 0-3 (aka big endian) userspace sees
a NULL pointer instead of the value it passed.



Fixing this while maintaining some modicum of sanity is the tricky bit.
The interface is made to kill_pid_usb_asyncio is made a sigval_t so the
standard signal compat tricks can be used.  sigval_t is a union of:

        int sival_int;
        void __user *sival_ptr;

         0   1  2   3    4   5   6  7
       +---+---+---+---+---+---+---+---+
       |            sival_ptr          | (64bit)
       +---+---+---+---+---+---+---+---+ 
       |    sival_ptr  | (32bit)
       +---+---+---+---+
       |    sival_int  |
       +---+---+---+---+

The signal code solves the compat issues for sigval_t by storing the
32bit pointers in sival_int.  So they meaningful bits are guaranteed to
be in the low 32bits, just like the 32bit sival_ptr.

After a bunch of build BUG_ONs to verify my reasonable assumptions
of but the siginfo layout are actually true, the code that generates
the siginfo just copies a sigval_t to si_pid.  And assumes the code
in the usb stack placed the pointer in the proper part of the sigval_t
when it read the information from userspace.

I don't know if that helps make it easy to understand but I figured I
would give it a shot.

Eric

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-22 21:50           ` Eric W. Biederman
@ 2019-05-23 18:12             ` Alan Stern
  2019-05-23 20:54               ` Eric W. Biederman
  0 siblings, 1 reply; 14+ messages in thread
From: Alan Stern @ 2019-05-23 18:12 UTC (permalink / raw)
  To: Eric W. Biederman
  Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

On Wed, 22 May 2019, Eric W. Biederman wrote:

> Perhaps this will work as a diagram.  I don't know if there is a better
> way to say it in my patch description.  In struct siginfo there are 3
> fields in fixed positions:
> 
>    int si_signo;
>    int si_errno;
>    int si_code;
> 
> After that there is a union.  The si_signo and si_code fields are
> examined to see which union member is valid (see siginfo_layout).
> In every other case a si_code of SI_ASYNCIO corresponds to
> the the _rt union member which has the fields:
> 
>    int si_pid;
>    int si_uid;
>    sigval_t si_sigval;

Yuu mean it's actually a union of structures containing these fields,
not a union of these fields, right?

> However when usb started using SI_ASYNCIO the _sigfault union member
> that (except for special exceptions) only has the field:
> 
>    void __user *si_addr;
> 
> Or in short the relevant piece of the union looks like:
> 
>          0   1  2   3    4   5   6  7
>        +---+---+---+---+---+---+---+---+
>        |    si_pid     |   si_uid      |
>        +---+---+---+---+---+---+---+---+
>        |             si_addr           | (64bit)
>        +---+---+---+---+---+---+---+---+
>        |     si_addr   | (32bit)
>        +---+---+---+---+
> 
> Which means if siginfo is copied field by field on 32bit everything
> works because si_pid and si_addr are in the same location.

When you say "copied field by field", you mean that the si_pid, 
si_uid, and si_sigval values are copied individually?

> Similarly if siginfo is copied field by field on 64bit everything
> works because there is no padding between si_pid and si_uid. So
> copying both of those fields results in the entire si_addr being
> copied.
> 
> It is the compat case that gets tricky.  Half of the bits are
> zero.  If those zero bits show up in bytes 4-7 and the data
> shows up in bytes 0-3 (aka little endian) everything works.
> If those zero bits show in in bytes 0-3 (aka big endian) userspace sees
> a NULL pointer instead of the value it passed.

The problem is that the compat translation layer copies si_pid and 
si_uid from the 64-bit kernel structure to the 32-bit user structure.  
And since the system is big-endian, the 64-bit si_addr value 
has zeros in bytes 0-3.  But those zeros are what userspace ends up 
seeing in its 32-bit version of si_addr.

So the solution is to store the address in the si_sigval part instead.  
Wouldn't it have been easier to have a compat routine somewhere just 
do something like:

	sinfo->si_pid = (u32) sinfo->si_addr;  /* Compensate for USB */

That would work regardless of the endianness, wouldn't it?

> Fixing this while maintaining some modicum of sanity is the tricky bit.
> The interface is made to kill_pid_usb_asyncio is made a sigval_t so the
> standard signal compat tricks can be used.  sigval_t is a union of:
> 
>         int sival_int;
>         void __user *sival_ptr;
> 
>          0   1  2   3    4   5   6  7
>        +---+---+---+---+---+---+---+---+
>        |            sival_ptr          | (64bit)
>        +---+---+---+---+---+---+---+---+ 
>        |    sival_ptr  | (32bit)
>        +---+---+---+---+
>        |    sival_int  |
>        +---+---+---+---+
> 
> The signal code solves the compat issues for sigval_t by storing the
> 32bit pointers in sival_int.  So they meaningful bits are guaranteed to
> be in the low 32bits, just like the 32bit sival_ptr.
> 
> After a bunch of build BUG_ONs to verify my reasonable assumptions
> of but the siginfo layout are actually true, the code that generates
> the siginfo just copies a sigval_t to si_pid.  And assumes the code
> in the usb stack placed the pointer in the proper part of the sigval_t
> when it read the information from userspace.
> 
> I don't know if that helps make it easy to understand but I figured I
> would give it a shot.

I think I understand now.  Thanks.

Alan


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-23 18:12             ` Alan Stern
@ 2019-05-23 20:54               ` Eric W. Biederman
  0 siblings, 0 replies; 14+ messages in thread
From: Eric W. Biederman @ 2019-05-23 20:54 UTC (permalink / raw)
  To: Alan Stern; +Cc: linux-usb, Greg Kroah-Hartman, Oliver Neukum, linux-kernel

Alan Stern <stern@rowland.harvard.edu> writes:

> On Wed, 22 May 2019, Eric W. Biederman wrote:
>
>> Perhaps this will work as a diagram.  I don't know if there is a better
>> way to say it in my patch description.  In struct siginfo there are 3
>> fields in fixed positions:
>> 
>>    int si_signo;
>>    int si_errno;
>>    int si_code;
>> 
>> After that there is a union.  The si_signo and si_code fields are
>> examined to see which union member is valid (see siginfo_layout).
>> In every other case a si_code of SI_ASYNCIO corresponds to
>> the the _rt union member which has the fields:
>> 
>>    int si_pid;
>>    int si_uid;
>>    sigval_t si_sigval;
>
> Yuu mean it's actually a union of structures containing these fields,
> not a union of these fields, right?

Correct.

>> However when usb started using SI_ASYNCIO the _sigfault union member
>> that (except for special exceptions) only has the field:
>> 
>>    void __user *si_addr;
>> 
>> Or in short the relevant piece of the union looks like:
>> 
>>          0   1  2   3    4   5   6  7
>>        +---+---+---+---+---+---+---+---+
>>        |    si_pid     |   si_uid      |
>>        +---+---+---+---+---+---+---+---+
>>        |             si_addr           | (64bit)
>>        +---+---+---+---+---+---+---+---+
>>        |     si_addr   | (32bit)
>>        +---+---+---+---+
>> 
>> Which means if siginfo is copied field by field on 32bit everything
>> works because si_pid and si_addr are in the same location.
>
> When you say "copied field by field", you mean that the si_pid, 
> si_uid, and si_sigval values are copied individually?

At least historically.  I have cleaned things up so no on native
we can do a byte by byte copy.  But for the compat case we still
copy field by field so that we have a chance of translating the fields.

>> Similarly if siginfo is copied field by field on 64bit everything
>> works because there is no padding between si_pid and si_uid. So
>> copying both of those fields results in the entire si_addr being
>> copied.
>> 
>> It is the compat case that gets tricky.  Half of the bits are
>> zero.  If those zero bits show up in bytes 4-7 and the data
>> shows up in bytes 0-3 (aka little endian) everything works.
>> If those zero bits show in in bytes 0-3 (aka big endian) userspace sees
>> a NULL pointer instead of the value it passed.
>
> The problem is that the compat translation layer copies si_pid and 
> si_uid from the 64-bit kernel structure to the 32-bit user structure.  
> And since the system is big-endian, the 64-bit si_addr value 
> has zeros in bytes 0-3.  But those zeros are what userspace ends up 
> seeing in its 32-bit version of si_addr.
>
> So the solution is to store the address in the si_sigval part instead.  
> Wouldn't it have been easier to have a compat routine somewhere just 
> do something like:
>
> 	sinfo->si_pid = (u32) sinfo->si_addr;  /* Compensate for USB */
>
> That would work regardless of the endianness, wouldn't it?

That logic will work yes.  That is essentially what I implemented in
proc_submiturb_compat:
	userurb_sigval.sival_int = ptr_to_compat(arg);

The practical problem is that we only know the compat status
of userspace when (a) the usb ioctl is called and when (b) the signal is
delivered to userspace.  We can't fix this a (b) because there is
too little information available due to the fact that glibc also
uses SI_ASYNCIO but with si_uid, si_pid, and si_siginfo. So we can't
just replace si_pid with sinfo->si_addr because si_pid and si_uid are
valid.

So we have to do something at (a) which is complicated by the fact
that we don't have siginfo yet.  So I choose to store a sigval_t
instead to just get the relevant bits.

>> Fixing this while maintaining some modicum of sanity is the tricky bit.
>> The interface is made to kill_pid_usb_asyncio is made a sigval_t so the
>> standard signal compat tricks can be used.  sigval_t is a union of:
>> 
>>         int sival_int;
>>         void __user *sival_ptr;
>> 
>>          0   1  2   3    4   5   6  7
>>        +---+---+---+---+---+---+---+---+
>>        |            sival_ptr          | (64bit)
>>        +---+---+---+---+---+---+---+---+ 
>>        |    sival_ptr  | (32bit)
>>        +---+---+---+---+
>>        |    sival_int  |
>>        +---+---+---+---+
>> 
>> The signal code solves the compat issues for sigval_t by storing the
>> 32bit pointers in sival_int.  So they meaningful bits are guaranteed to
>> be in the low 32bits, just like the 32bit sival_ptr.
>> 
>> After a bunch of build BUG_ONs to verify my reasonable assumptions
>> of but the siginfo layout are actually true, the code that generates
>> the siginfo just copies a sigval_t to si_pid.  And assumes the code
>> in the usb stack placed the pointer in the proper part of the sigval_t
>> when it read the information from userspace.
>> 
>> I don't know if that helps make it easy to understand but I figured I
>> would give it a shot.
>
> I think I understand now.  Thanks.

Welcome.  I am trying to make this stuff comprehensible.

Eric


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio
  2019-05-21 12:40       ` [PATCH] " Eric W. Biederman
  2019-05-21 14:02         ` Alan Stern
  2019-05-22 19:02         ` Alan Stern
@ 2019-06-06 14:27         ` Greg Kroah-Hartman
  2 siblings, 0 replies; 14+ messages in thread
From: Greg Kroah-Hartman @ 2019-06-06 14:27 UTC (permalink / raw)
  To: Eric W. Biederman; +Cc: Alan Stern, linux-usb, Oliver Neukum, linux-kernel

On Tue, May 21, 2019 at 07:40:35AM -0500, Eric W. Biederman wrote:
> 
> The usb support for asyncio encoded one of it's values in the wrong
> field.  It should have used si_value but instead used si_addr which is
> not present in the _rt union member of struct siginfo.
> 
> The practical result of this is that on a 64bit big endian kernel
> when delivering a signal to a 32bit process the si_addr field
> is set to NULL, instead of the expected pointer value.
> 
> This issue can not be fixed in copy_siginfo_to_user32 as the usb
> usage of the the _sigfault (aka si_addr) member of the siginfo
> union when SI_ASYNCIO is set is incompatible with the POSIX and
> glibc usage of the _rt member of the siginfo union.
> 
> Therefore replace kill_pid_info_as_cred with kill_pid_usb_asyncio a
> dedicated function for this one specific case.  There are no other
> users of kill_pid_info_as_cred so this specialization should have no
> impact on the amount of code in the kernel.  Have kill_pid_usb_asyncio
> take instead of a siginfo_t which is difficult and error prone, 3
> arguments, a signal number, an errno value, and an address enconded as
> a sigval_t.  The encoding of the address as a sigval_t allows the
> code that reads the userspace request for a signal to handle this
> compat issue along with all of the other compat issues.
> 
> Add BUILD_BUG_ONs in kernel/signal.c to ensure that we can now place
> the pointer value at the in si_pid (instead of si_addr).  That is the
> code now verifies that si_pid and si_addr always occur at the same
> location.  Further the code veries that for native structures a value
> placed in si_pid and spilling into si_uid will appear in userspace in
> si_addr (on a byte by byte copy of siginfo or a field by field copy of
> siginfo).  The code also verifies that for a 64bit kernel and a 32bit
> userspace the 32bit pointer will fit in si_pid.
> 
> I have used the usbsig.c program below written by Alan Stern and
> slightly tweaked by me to run on a big endian machine to verify the
> issue exists (on sparc64) and to confirm the patch below fixes the issue.
> 
> /* usbsig.c -- test USB async signal delivery */
> 
> static struct usbdevfs_urb urb;
> static struct usbdevfs_disconnectsignal ds;
> static volatile sig_atomic_t done = 0;
> 
> void urb_handler(int sig, siginfo_t *info , void *ucontext)
> {
> 	printf("Got signal %d, signo %d errno %d code %d addr: %p urb: %p\n",
> 	       sig, info->si_signo, info->si_errno, info->si_code,
> 	       info->si_addr, &urb);
> 
> 	printf("%s\n", (info->si_addr == &urb) ? "Good" : "Bad");
> }
> 
> void ds_handler(int sig, siginfo_t *info , void *ucontext)
> {
> 	printf("Got signal %d, signo %d errno %d code %d addr: %p ds: %p\n",
> 	       sig, info->si_signo, info->si_errno, info->si_code,
> 	       info->si_addr, &ds);
> 
> 	printf("%s\n", (info->si_addr == &ds) ? "Good" : "Bad");
> 	done = 1;
> }
> 
> int main(int argc, char **argv)
> {
> 	char *devfilename;
> 	int fd;
> 	int rc;
> 	struct sigaction act;
> 	struct usb_ctrlrequest *req;
> 	void *ptr;
> 	char buf[80];
> 
> 	if (argc != 2) {
> 		fprintf(stderr, "Usage: usbsig device-file-name\n");
> 		return 1;
> 	}
> 
> 	devfilename = argv[1];
> 	fd = open(devfilename, O_RDWR);
> 	if (fd == -1) {
> 		perror("Error opening device file");
> 		return 1;
> 	}
> 
> 	act.sa_sigaction = urb_handler;
> 	sigemptyset(&act.sa_mask);
> 	act.sa_flags = SA_SIGINFO;
> 
> 	rc = sigaction(SIGUSR1, &act, NULL);
> 	if (rc == -1) {
> 		perror("Error in sigaction");
> 		return 1;
> 	}
> 
> 	act.sa_sigaction = ds_handler;
> 	sigemptyset(&act.sa_mask);
> 	act.sa_flags = SA_SIGINFO;
> 
> 	rc = sigaction(SIGUSR2, &act, NULL);
> 	if (rc == -1) {
> 		perror("Error in sigaction");
> 		return 1;
> 	}
> 
> 	memset(&urb, 0, sizeof(urb));
> 	urb.type = USBDEVFS_URB_TYPE_CONTROL;
> 	urb.endpoint = USB_DIR_IN | 0;
> 	urb.buffer = buf;
> 	urb.buffer_length = sizeof(buf);
> 	urb.signr = SIGUSR1;
> 
> 	req = (struct usb_ctrlrequest *) buf;
> 	req->bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
> 	req->bRequest = USB_REQ_GET_DESCRIPTOR;
> 	req->wValue = htole16(USB_DT_DEVICE << 8);
> 	req->wIndex = htole16(0);
> 	req->wLength = htole16(sizeof(buf) - sizeof(*req));
> 
> 	rc = ioctl(fd, USBDEVFS_SUBMITURB, &urb);
> 	if (rc == -1) {
> 		perror("Error in SUBMITURB ioctl");
> 		return 1;
> 	}
> 
> 	rc = ioctl(fd, USBDEVFS_REAPURB, &ptr);
> 	if (rc == -1) {
> 		perror("Error in REAPURB ioctl");
> 		return 1;
> 	}
> 
> 	memset(&ds, 0, sizeof(ds));
> 	ds.signr = SIGUSR2;
> 	ds.context = &ds;
> 	rc = ioctl(fd, USBDEVFS_DISCSIGNAL, &ds);
> 	if (rc == -1) {
> 		perror("Error in DISCSIGNAL ioctl");
> 		return 1;
> 	}
> 
> 	printf("Waiting for usb disconnect\n");
> 	while (!done) {
> 		sleep(1);
> 	}
> 
> 	close(fd);
> 	return 0;
> }
> 
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: linux-usb@vger.kernel.org
> Cc: Alan Stern <stern@rowland.harvard.edu>
> Cc: Oliver Neukum <oneukum@suse.com>
> Fixes: v2.3.39
> Cc: stable@vger.kernel.org
> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
> ---

Ugh, what a mess, thanks for doing this work:

Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2019-06-06 14:27 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-02-08  2:34 [CFT][PATCH] signal/usb: Replace kill_pid_info_as_cred with kill_pid_usb_asyncio Eric W. Biederman
2019-02-08 21:57 ` Alan Stern
2019-05-18  1:38   ` Eric W. Biederman
2019-05-18 15:20     ` Alan Stern
2019-05-18 15:25       ` Eric W. Biederman
2019-05-21 12:40       ` [PATCH] " Eric W. Biederman
2019-05-21 14:02         ` Alan Stern
2019-05-21 14:47           ` Eric W. Biederman
2019-05-21 15:30             ` Alan Stern
2019-05-22 19:02         ` Alan Stern
2019-05-22 21:50           ` Eric W. Biederman
2019-05-23 18:12             ` Alan Stern
2019-05-23 20:54               ` Eric W. Biederman
2019-06-06 14:27         ` Greg Kroah-Hartman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).