linux-stable/include/net/phonet/phonet.h
Breno Leitao e1d001fa5b net: ioctl: Use kernel memory on protocol ioctl callbacks
Most of the ioctls to net protocols operates directly on userspace
argument (arg). Usually doing get_user()/put_user() directly in the
ioctl callback.  This is not flexible, because it is hard to reuse these
functions without passing userspace buffers.

Change the "struct proto" ioctls to avoid touching userspace memory and
operate on kernel buffers, i.e., all protocol's ioctl callbacks is
adapted to operate on a kernel memory other than on userspace (so, no
more {put,get}_user() and friends being called in the ioctl callback).

This changes the "struct proto" ioctl format in the following way:

    int                     (*ioctl)(struct sock *sk, int cmd,
-                                        unsigned long arg);
+                                        int *karg);

(Important to say that this patch does not touch the "struct proto_ops"
protocols)

So, the "karg" argument, which is passed to the ioctl callback, is a
pointer allocated to kernel space memory (inside a function wrapper).
This buffer (karg) may contain input argument (copied from userspace in
a prep function) and it might return a value/buffer, which is copied
back to userspace if necessary. There is not one-size-fits-all format
(that is I am using 'may' above), but basically, there are three type of
ioctls:

1) Do not read from userspace, returns a result to userspace
2) Read an input parameter from userspace, and does not return anything
  to userspace
3) Read an input from userspace, and return a buffer to userspace.

The default case (1) (where no input parameter is given, and an "int" is
returned to userspace) encompasses more than 90% of the cases, but there
are two other exceptions. Here is a list of exceptions:

* Protocol RAW:
   * cmd = SIOCGETVIFCNT:
     * input and output = struct sioc_vif_req
   * cmd = SIOCGETSGCNT
     * input and output = struct sioc_sg_req
   * Explanation: for the SIOCGETVIFCNT case, userspace passes the input
     argument, which is struct sioc_vif_req. Then the callback populates
     the struct, which is copied back to userspace.

* Protocol RAW6:
   * cmd = SIOCGETMIFCNT_IN6
     * input and output = struct sioc_mif_req6
   * cmd = SIOCGETSGCNT_IN6
     * input and output = struct sioc_sg_req6

* Protocol PHONET:
  * cmd == SIOCPNADDRESOURCE | SIOCPNDELRESOURCE
     * input int (4 bytes)
  * Nothing is copied back to userspace.

For the exception cases, functions sock_sk_ioctl_inout() will
copy the userspace input, and copy it back to kernel space.

The wrapper that prepare the buffer and put the buffer back to user is
sk_ioctl(), so, instead of calling sk->sk_prot->ioctl(), the callee now
calls sk_ioctl(), which will handle all cases.

Signed-off-by: Breno Leitao <leitao@debian.org>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Reviewed-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Link: https://lore.kernel.org/r/20230609152800.830401-1-leitao@debian.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2023-06-15 22:33:26 -07:00

133 lines
3.2 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* File: af_phonet.h
*
* Phonet sockets kernel definitions
*
* Copyright (C) 2008 Nokia Corporation.
*/
#ifndef AF_PHONET_H
#define AF_PHONET_H
#include <linux/phonet.h>
#include <linux/skbuff.h>
#include <net/sock.h>
/*
* The lower layers may not require more space, ever. Make sure it's
* enough.
*/
#define MAX_PHONET_HEADER (8 + MAX_HEADER)
/*
* Every Phonet* socket has this structure first in its
* protocol-specific structure under name c.
*/
struct pn_sock {
struct sock sk;
u16 sobject;
u16 dobject;
u8 resource;
};
static inline struct pn_sock *pn_sk(struct sock *sk)
{
return (struct pn_sock *)sk;
}
extern const struct proto_ops phonet_dgram_ops;
void pn_sock_init(void);
struct sock *pn_find_sock_by_sa(struct net *net, const struct sockaddr_pn *sa);
void pn_deliver_sock_broadcast(struct net *net, struct sk_buff *skb);
void phonet_get_local_port_range(int *min, int *max);
int pn_sock_hash(struct sock *sk);
void pn_sock_unhash(struct sock *sk);
int pn_sock_get_port(struct sock *sk, unsigned short sport);
struct sock *pn_find_sock_by_res(struct net *net, u8 res);
int pn_sock_bind_res(struct sock *sock, u8 res);
int pn_sock_unbind_res(struct sock *sk, u8 res);
void pn_sock_unbind_all_res(struct sock *sk);
int pn_skb_send(struct sock *sk, struct sk_buff *skb,
const struct sockaddr_pn *target);
static inline struct phonethdr *pn_hdr(struct sk_buff *skb)
{
return (struct phonethdr *)skb_network_header(skb);
}
static inline struct phonetmsg *pn_msg(struct sk_buff *skb)
{
return (struct phonetmsg *)skb_transport_header(skb);
}
/*
* Get the other party's sockaddr from received skb. The skb begins
* with a Phonet header.
*/
static inline
void pn_skb_get_src_sockaddr(struct sk_buff *skb, struct sockaddr_pn *sa)
{
struct phonethdr *ph = pn_hdr(skb);
u16 obj = pn_object(ph->pn_sdev, ph->pn_sobj);
sa->spn_family = AF_PHONET;
pn_sockaddr_set_object(sa, obj);
pn_sockaddr_set_resource(sa, ph->pn_res);
memset(sa->spn_zero, 0, sizeof(sa->spn_zero));
}
static inline
void pn_skb_get_dst_sockaddr(struct sk_buff *skb, struct sockaddr_pn *sa)
{
struct phonethdr *ph = pn_hdr(skb);
u16 obj = pn_object(ph->pn_rdev, ph->pn_robj);
sa->spn_family = AF_PHONET;
pn_sockaddr_set_object(sa, obj);
pn_sockaddr_set_resource(sa, ph->pn_res);
memset(sa->spn_zero, 0, sizeof(sa->spn_zero));
}
/* Protocols in Phonet protocol family. */
struct phonet_protocol {
const struct proto_ops *ops;
struct proto *prot;
int sock_type;
};
int phonet_proto_register(unsigned int protocol,
const struct phonet_protocol *pp);
void phonet_proto_unregister(unsigned int protocol,
const struct phonet_protocol *pp);
int phonet_sysctl_init(void);
void phonet_sysctl_exit(void);
int isi_register(void);
void isi_unregister(void);
static inline bool sk_is_phonet(struct sock *sk)
{
return sk->sk_family == PF_PHONET;
}
static inline int phonet_sk_ioctl(struct sock *sk, unsigned int cmd,
void __user *arg)
{
int karg;
switch (cmd) {
case SIOCPNADDRESOURCE:
case SIOCPNDELRESOURCE:
if (get_user(karg, (int __user *)arg))
return -EFAULT;
return sk->sk_prot->ioctl(sk, cmd, &karg);
}
/* A positive return value means that the ioctl was not processed */
return 1;
}
#endif