root/net/atm/ioctl.c
// SPDX-License-Identifier: GPL-2.0
/* ATM ioctl handling */

/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
/* 2003 John Levon  <levon@movementarian.org> */

#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__

#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/net.h>          /* struct socket, struct proto_ops */
#include <linux/atm.h>          /* ATM stuff */
#include <linux/atmdev.h>
#include <linux/atmclip.h>      /* CLIP_*ENCAP */
#include <linux/atmarp.h>       /* manifest constants */
#include <linux/capability.h>
#include <linux/sonet.h>        /* for ioctls */
#include <linux/atmsvc.h>
#include <linux/atmmpc.h>
#include <net/atmclip.h>
#include <linux/atmlec.h>
#include <linux/mutex.h>
#include <asm/ioctls.h>
#include <net/compat.h>

#include "resources.h"
#include "signaling.h"          /* for WAITING and sigd_attach */
#include "common.h"


static DEFINE_MUTEX(ioctl_mutex);
static LIST_HEAD(ioctl_list);


void register_atm_ioctl(struct atm_ioctl *ioctl)
{
        mutex_lock(&ioctl_mutex);
        list_add_tail(&ioctl->list, &ioctl_list);
        mutex_unlock(&ioctl_mutex);
}
EXPORT_SYMBOL(register_atm_ioctl);

void deregister_atm_ioctl(struct atm_ioctl *ioctl)
{
        mutex_lock(&ioctl_mutex);
        list_del(&ioctl->list);
        mutex_unlock(&ioctl_mutex);
}
EXPORT_SYMBOL(deregister_atm_ioctl);

static int do_vcc_ioctl(struct socket *sock, unsigned int cmd,
                        unsigned long arg, int compat)
{
        struct sock *sk = sock->sk;
        struct atm_vcc *vcc;
        int error;
        struct list_head *pos;
        void __user *argp = (void __user *)arg;
        void __user *buf;
        int __user *len;

        vcc = ATM_SD(sock);
        switch (cmd) {
        case SIOCOUTQ:
                if (sock->state != SS_CONNECTED ||
                    !test_bit(ATM_VF_READY, &vcc->flags)) {
                        error =  -EINVAL;
                        goto done;
                }
                error = put_user(sk->sk_sndbuf - sk_wmem_alloc_get(sk),
                                 (int __user *)argp);
                goto done;
        case SIOCINQ:
        {
                struct sk_buff *skb;
                int amount;

                if (sock->state != SS_CONNECTED) {
                        error = -EINVAL;
                        goto done;
                }
                spin_lock_irq(&sk->sk_receive_queue.lock);
                skb = skb_peek(&sk->sk_receive_queue);
                amount = skb ? skb->len : 0;
                spin_unlock_irq(&sk->sk_receive_queue.lock);
                error = put_user(amount, (int __user *)argp);
                goto done;
        }
        case ATM_SETSC:
                net_warn_ratelimited("ATM_SETSC is obsolete; used by %s:%d\n",
                                     current->comm, task_pid_nr(current));
                error = 0;
                goto done;
        case ATMSIGD_CTRL:
                if (!capable(CAP_NET_ADMIN)) {
                        error = -EPERM;
                        goto done;
                }
                /*
                 * The user/kernel protocol for exchanging signalling
                 * info uses kernel pointers as opaque references,
                 * so the holder of the file descriptor can scribble
                 * on the kernel... so we should make sure that we
                 * have the same privileges that /proc/kcore needs
                 */
                if (!capable(CAP_SYS_RAWIO)) {
                        error = -EPERM;
                        goto done;
                }
#ifdef CONFIG_COMPAT
                /* WTF? I don't even want to _think_ about making this
                   work for 32-bit userspace. TBH I don't really want
                   to think about it at all. dwmw2. */
                if (compat) {
                        net_warn_ratelimited("32-bit task cannot be atmsigd\n");
                        error = -EINVAL;
                        goto done;
                }
#endif
                error = sigd_attach(vcc);
                if (!error)
                        sock->state = SS_CONNECTED;
                goto done;
        case ATM_SETBACKEND:
        case ATM_NEWBACKENDIF:
        {
                atm_backend_t backend;
                error = get_user(backend, (atm_backend_t __user *)argp);
                if (error)
                        goto done;
                switch (backend) {
                case ATM_BACKEND_PPP:
                        request_module("pppoatm");
                        break;
                case ATM_BACKEND_BR2684:
                        request_module("br2684");
                        break;
                }
                break;
        }
        case ATMMPC_CTRL:
        case ATMMPC_DATA:
                request_module("mpoa");
                break;
        case ATMARPD_CTRL:
                request_module("clip");
                break;
        case ATMLEC_CTRL:
                request_module("lec");
                break;
        }

        error = -ENOIOCTLCMD;

        mutex_lock(&ioctl_mutex);
        list_for_each(pos, &ioctl_list) {
                struct atm_ioctl *ic = list_entry(pos, struct atm_ioctl, list);
                if (try_module_get(ic->owner)) {
                        error = ic->ioctl(sock, cmd, arg);
                        module_put(ic->owner);
                        if (error != -ENOIOCTLCMD)
                                break;
                }
        }
        mutex_unlock(&ioctl_mutex);

        if (error != -ENOIOCTLCMD)
                goto done;

        if (cmd == ATM_GETNAMES) {
                if (IS_ENABLED(CONFIG_COMPAT) && compat) {
#ifdef CONFIG_COMPAT
                        struct compat_atm_iobuf __user *ciobuf = argp;
                        compat_uptr_t cbuf;
                        len = &ciobuf->length;
                        if (get_user(cbuf, &ciobuf->buffer))
                                return -EFAULT;
                        buf = compat_ptr(cbuf);
#endif
                } else {
                        struct atm_iobuf __user *iobuf = argp;
                        len = &iobuf->length;
                        if (get_user(buf, &iobuf->buffer))
                                return -EFAULT;
                }
                error = atm_getnames(buf, len);
        } else {
                int number;

                if (IS_ENABLED(CONFIG_COMPAT) && compat) {
#ifdef CONFIG_COMPAT
                        struct compat_atmif_sioc __user *csioc = argp;
                        compat_uptr_t carg;

                        len = &csioc->length;
                        if (get_user(carg, &csioc->arg))
                                return -EFAULT;
                        buf = compat_ptr(carg);
                        if (get_user(number, &csioc->number))
                                return -EFAULT;
#endif
                } else {
                        struct atmif_sioc __user *sioc = argp;

                        len = &sioc->length;
                        if (get_user(buf, &sioc->arg))
                                return -EFAULT;
                        if (get_user(number, &sioc->number))
                                return -EFAULT;
                }
                error = atm_dev_ioctl(cmd, buf, len, number, compat);
        }

done:
        return error;
}

int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
        return do_vcc_ioctl(sock, cmd, arg, 0);
}

#ifdef CONFIG_COMPAT
/*
 * FIXME:
 * The compat_ioctl handling is duplicated, using both these conversion
 * routines and the compat argument to the actual handlers. Both
 * versions are somewhat incomplete and should be merged, e.g. by
 * moving the ioctl number translation into the actual handlers and
 * killing the conversion code.
 *
 * -arnd, November 2009
 */
#define ATM_GETLINKRATE32 _IOW('a', ATMIOC_ITF+1, struct compat_atmif_sioc)
#define ATM_GETNAMES32    _IOW('a', ATMIOC_ITF+3, struct compat_atm_iobuf)
#define ATM_GETTYPE32     _IOW('a', ATMIOC_ITF+4, struct compat_atmif_sioc)
#define ATM_GETESI32      _IOW('a', ATMIOC_ITF+5, struct compat_atmif_sioc)
#define ATM_GETADDR32     _IOW('a', ATMIOC_ITF+6, struct compat_atmif_sioc)
#define ATM_RSTADDR32     _IOW('a', ATMIOC_ITF+7, struct compat_atmif_sioc)
#define ATM_ADDADDR32     _IOW('a', ATMIOC_ITF+8, struct compat_atmif_sioc)
#define ATM_DELADDR32     _IOW('a', ATMIOC_ITF+9, struct compat_atmif_sioc)
#define ATM_GETCIRANGE32  _IOW('a', ATMIOC_ITF+10, struct compat_atmif_sioc)
#define ATM_SETCIRANGE32  _IOW('a', ATMIOC_ITF+11, struct compat_atmif_sioc)
#define ATM_SETESI32      _IOW('a', ATMIOC_ITF+12, struct compat_atmif_sioc)
#define ATM_SETESIF32     _IOW('a', ATMIOC_ITF+13, struct compat_atmif_sioc)
#define ATM_GETSTAT32     _IOW('a', ATMIOC_SARCOM+0, struct compat_atmif_sioc)
#define ATM_GETSTATZ32    _IOW('a', ATMIOC_SARCOM+1, struct compat_atmif_sioc)
#define ATM_GETLOOP32     _IOW('a', ATMIOC_SARCOM+2, struct compat_atmif_sioc)
#define ATM_SETLOOP32     _IOW('a', ATMIOC_SARCOM+3, struct compat_atmif_sioc)
#define ATM_QUERYLOOP32   _IOW('a', ATMIOC_SARCOM+4, struct compat_atmif_sioc)

static struct {
        unsigned int cmd32;
        unsigned int cmd;
} atm_ioctl_map[] = {
        { ATM_GETLINKRATE32, ATM_GETLINKRATE },
        { ATM_GETNAMES32,    ATM_GETNAMES },
        { ATM_GETTYPE32,     ATM_GETTYPE },
        { ATM_GETESI32,      ATM_GETESI },
        { ATM_GETADDR32,     ATM_GETADDR },
        { ATM_RSTADDR32,     ATM_RSTADDR },
        { ATM_ADDADDR32,     ATM_ADDADDR },
        { ATM_DELADDR32,     ATM_DELADDR },
        { ATM_GETCIRANGE32,  ATM_GETCIRANGE },
        { ATM_SETCIRANGE32,  ATM_SETCIRANGE },
        { ATM_SETESI32,      ATM_SETESI },
        { ATM_SETESIF32,     ATM_SETESIF },
        { ATM_GETSTAT32,     ATM_GETSTAT },
        { ATM_GETSTATZ32,    ATM_GETSTATZ },
        { ATM_GETLOOP32,     ATM_GETLOOP },
        { ATM_SETLOOP32,     ATM_SETLOOP },
        { ATM_QUERYLOOP32,   ATM_QUERYLOOP },
};

#define NR_ATM_IOCTL ARRAY_SIZE(atm_ioctl_map)

static int do_atm_iobuf(struct socket *sock, unsigned int cmd,
                        unsigned long arg)
{
        struct compat_atm_iobuf __user *iobuf32 = compat_ptr(arg);
        u32 data;

        if (get_user(data, &iobuf32->buffer))
                return -EFAULT;

        return atm_getnames(&iobuf32->length, compat_ptr(data));
}

static int do_atmif_sioc(struct socket *sock, unsigned int cmd,
                         unsigned long arg)
{
        struct compat_atmif_sioc __user *sioc32 = compat_ptr(arg);
        int number;
        u32 data;

        if (get_user(data, &sioc32->arg) || get_user(number, &sioc32->number))
                return -EFAULT;
        return atm_dev_ioctl(cmd, compat_ptr(data), &sioc32->length, number, 0);
}

static int do_atm_ioctl(struct socket *sock, unsigned int cmd32,
                        unsigned long arg)
{
        int i;
        unsigned int cmd = 0;

        switch (cmd32) {
        case SONET_GETSTAT:
        case SONET_GETSTATZ:
        case SONET_GETDIAG:
        case SONET_SETDIAG:
        case SONET_CLRDIAG:
        case SONET_SETFRAMING:
        case SONET_GETFRAMING:
        case SONET_GETFRSENSE:
                return do_atmif_sioc(sock, cmd32, arg);
        }

        for (i = 0; i < NR_ATM_IOCTL; i++) {
                if (cmd32 == atm_ioctl_map[i].cmd32) {
                        cmd = atm_ioctl_map[i].cmd;
                        break;
                }
        }
        if (i == NR_ATM_IOCTL)
                return -EINVAL;

        switch (cmd) {
        case ATM_GETNAMES:
                return do_atm_iobuf(sock, cmd, arg);

        case ATM_GETLINKRATE:
        case ATM_GETTYPE:
        case ATM_GETESI:
        case ATM_GETADDR:
        case ATM_RSTADDR:
        case ATM_ADDADDR:
        case ATM_DELADDR:
        case ATM_GETCIRANGE:
        case ATM_SETCIRANGE:
        case ATM_SETESI:
        case ATM_SETESIF:
        case ATM_GETSTAT:
        case ATM_GETSTATZ:
        case ATM_GETLOOP:
        case ATM_SETLOOP:
        case ATM_QUERYLOOP:
                return do_atmif_sioc(sock, cmd, arg);
        }

        return -EINVAL;
}

int vcc_compat_ioctl(struct socket *sock, unsigned int cmd,
                     unsigned long arg)
{
        int ret;

        ret = do_vcc_ioctl(sock, cmd, arg, 1);
        if (ret != -ENOIOCTLCMD)
                return ret;

        return do_atm_ioctl(sock, cmd, arg);
}
#endif