Re: [PATCH v3 02/26] compat_ioctl: move simple ppp command handling into driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Apr 18, 2019 at 12:03:07AM +0200, Arnd Bergmann wrote:
> On Wed, Apr 17, 2019 at 11:13 PM Al Viro <viro@xxxxxxxxxxxxxxxxxx> wrote:
> >
> > On Tue, Apr 16, 2019 at 10:19:40PM +0200, Arnd Bergmann wrote:
> > > diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
> > > index c708400fff4a..04252c3492ee 100644
> > > --- a/drivers/net/ppp/ppp_generic.c
> > > +++ b/drivers/net/ppp/ppp_generic.c
> > > @@ -899,6 +899,7 @@ static const struct file_operations ppp_device_fops = {
> > >       .write          = ppp_write,
> > >       .poll           = ppp_poll,
> > >       .unlocked_ioctl = ppp_ioctl,
> > > +     .compat_ioctl   = ppp_ioctl,
> >
> > Oh?  What happens on e.g. s390 with something like PPPIOCNEWUNIT?
> > Current kernel:
> >         * no ->compat_ioctl()
> >         * ->unlock_ioctl() is present
> >         * found by compat_ioctl_check_table()
> >         * pass (unsigned long)compat_ptr(arg) to do_vfs_ioctl()
> >         * pass that to ppp_ioctl()
> >         * pass that to ppp_unattached_ioctl()
> >         * fetch int from (int __user *)compat_ptr(arg)
> >
> > With your patch:
> >         * call ppp_ioctl()
> >         * pass arg to ppp_unattached_ioctl()
> >         * fetch int from (int __user *)arg
> >
> > AFAICS, that's broken...
> 
> Correct. I had added this patch to the series from an older set of
> patches (predating
> the compat_ptr_ioctl() series) , and did not check for this issue again. When
> I originally created the patch, I assumed that even on s390 it would be no
> problem.
> 
> > Looking at that ppp_ioctl(),
> > pointer to arch-independent type or ignored:
> >         PPPIOCNEWUNIT PPPIOCATTACH PPPIOCATTCHAN PPPIOCSMRU PPPIOCSFLAGS
> >         PPPIOCGFLAGS PPPIOCGUNIT PPPIOCSDEBUG PPPIOCSMAXCID PPPIOCCONNECT
> >         PPPIOCGDEBUG PPPIOCSMAXCID PPPIOCSMRRU
> >         PPPIOCDETACH PPPIOCDISCONN
> >         PPPIOCGASYNCMAP PPPIOCSASYNCMAP PPPIOCGRASYNCMAP PPPIOCSRASYNCMAP
> >         PPPIOCGXASYNCMAP PPPIOCSXASYNCMAP
> >         PPPIOCGNPMODE PPPIOCSNPMODE
> > pointer to struct ppp_option_data (with further pointer-chasing in it):
> >         PPPIOCSCOMPRESS
> > pointer to struct ppp_idle:
> >         PPPIOCGIDLE
> > pointer to struct sock_filter (with hidden pointer-chasing, AFAICS):
> >         PPPIOCSPASS PPPIOCSACTIVE
> >
> > Pretty much all of them take pointers.  What's more, reaction to
> > unknown is -ENOTTY, not -ENOIOCTLCM, so that patch will have
> > prevent the translated ones from reaching do_ioctl_trans()
> 
> Good point, this patch sequence does break bisection.
> 
> > What am I missing here?  Why not simply do
> >
> > compat_ppp_ioctl()
> > {
> >         PPPIOCSCOMPRESS32 => deal with it
> >         PPPIOCGIDLE32 => deal with it
> >         PPPIOCSPASS32 / PPPIOCSACTIVE32 => deal with it
> >         default: pass compat_ptr(arg) to ppp_ioctl() and be done with that
> > }
> >
> > with BPF-related bits (both compat and native) taken to e.g. net/core/bpf-ppp.c,
> > picked by both generic and isdn?  IDGI...
> 
> I was trying to unify the native and compat code paths as much
> as possible here. Handling the four PPPIO*32 commands in
> compat_ppp_ioctl would either require duplicating large chunks
> of ppp_ioctl, or keeping the extra compat_alloc_user_space()
> copy from the existing implementation.
> 
> I'll try to come up with a different way to structure the patches.

Huh?  Instead of
        case PPPIOCSCOMPRESS:
                err = ppp_set_compress(ppp, arg);
                break;
in native, have
	struct ppp_option_data data;
...
        case PPPIOCSCOMPRESS:
		if (copy_from_user(&data, argp, sizeof(data)))
			err = -EFAULT;
		else
			err = ppp_set_compress(ppp, &data);
                break;

while in compat do
	struct ppp_option_data32 data32;

        case PPPIOCSCOMPRESS32:
		if (copy_from_user(&data32, argp, sizeof(data32)))
			err = -EFAULT;
		else
			err = ppp_set_compress(ppp, &(struct ppp_option_data){
							.ptr = compat_ptr(data32.ptr),
							.length = data32.length,
							.transmit = data32.transmit
						});
		break;

PPPIOCGIDLE is small to start with - not a lot to copy there.
And as for the filters...  What you need is something like

struct bpf_prog *get_ppp_bpf(struct sock_fprog __user *p)
{
        struct sock_fprog uprog;
	struct sock_fprog_kern fprog;
	struct sock_filter *code = NULL;
	struct bpf_prog *res;
        int err;

        if (copy_from_user(&uprog, p, sizeof(uprog)))
                return ERR_PTR(-EFAULT);

        if (!uprog.len)
                return NULL;

        fprog.len = uprog.len * sizeof(struct sock_filter);
        code = fprog.filter = memdup_user(uprog.filter, fprog.len);
        if (IS_ERR(code))
                return ERR_CAST(code);

	err = bpf_prog_create(&res, &fprog);
	kfree(code);

	if (err)
		return ERR_PTR(err);
	return res;
}
in net/core/ppp-filter.c (or in net/core/filter.c, for that matter, under
appropriate ifdef)) with obvious compat counterpart next to it.  Hell,
turn the above into

static struct bpf_prog *__get_ppp_bpf(struct sock_fprog *kp)
{
	struct sock_fprog_kern fprog;
	struct sock_filter *code = NULL;
	struct bpf_prog *res;
        int err;

        if (!kp->len)
                return NULL;

        fprog.len = kp->len * sizeof(struct sock_filter);
        code = fprog.filter = memdup_user(kp->filter, fprog.len);
        if (IS_ERR(code))
                return ERR_CAST(code);

	err = bpf_prog_create(&res, &fprog);
	kfree(code);

	if (err)
		return ERR_PTR(err);
	return res;
}

struct bpf_prog *get_ppp_bpf(struct sock_fprog __user *p)
{
        struct sock_fprog uprog;

        if (copy_from_user(&uprog, p, sizeof(uprog)))
                return ERR_PTR(-EFAULT);
	return __get_ppp_bpf(&uprog);
}

struct bpf_prog *compat_get_ppp_bpf(struct sock_fprog32 __user *p)
{
        struct sock_fprog uprog32;

        if (copy_from_user(&uprog32, p, sizeof(uprog32)))
                return ERR_PTR(-EFAULT);
	return __get_ppp_bpf(&(struct sock_fprog){
			.len = uprog32.len,
			.filter = compat_ptr(uprog32.filter)});
}

Then in native ioctl do
        case PPPIOCSPASS:
        case PPPIOCSACTIVE:
        {
                struct bpf_prog *filter = get_bpf_ppp(argp);

		if (IS_ERR(filter)) {
			err = PTR_ERR(filter);
		} else {
			struct bpf_prog **which;
			if (cmd == PPPIOCSPASS)
				which = &ppp->pass_filter;
			else
				which = &ppp->active_filter;
			ppp_lock(ppp);
			if (*which)
				bpf_prog_destroy(*which);
			*which = filter;
			ppp_unlock(ppp);
			err = 0;
		}
                break;
	}
in native and similar in compat, with get_bpf_ppp() replaced
with call of compat_get_bpf_ppp() and ioctl numbers obviously
adjusted.  All there is to it...  Helpers obviously shared
with isdn and yes, all crap gone from fs/compat_ioctl.c...

Why would you want to duplicate large chunks of anything?
The above is not even compile-tested, but...  I can put
together a patch if you wish.  Or am I missing something
here?



[Index of Archives]     [Linux Audio Users]     [Linux for Hams]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Fedora Users]

  Powered by Linux