@@ -155,6 +155,10 @@ extern unsigned int sk_run_filter(const struct sk_buff *skb,
const struct sock_filter *filter);
extern int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk);
extern int sk_detach_filter(struct sock *sk);
+#ifdef CONFIG_UNIX_MULTICAST
+extern int sk_attach_peer_filter(struct sock_fprog *fprog, struct sock *sk);
+extern int sk_detach_peer_filter(struct sock *sk);
+#endif
extern int sk_chk_filter(struct sock_filter *filter, unsigned int flen);
#ifdef CONFIG_BPF_JIT
@@ -49,6 +49,7 @@ struct unix_skb_parms {
#define UNIX_JOIN_GROUP 2
#define UNIX_LEAVE_GROUP 3
#define UNIX_ACCEPT_GROUP 4
+#define UNIX_ATTACH_FILTER 5
/* Flags on unix_mreq */
@@ -237,6 +237,7 @@ struct cg_proto;
* @sk_sndtimeo: %SO_SNDTIMEO setting
* @sk_rxhash: flow hash received from netif layer
* @sk_filter: socket filtering instructions
+ * @sk_peer_filter: peer socket filtering instructions
* @sk_protinfo: private area, net family specific, when not using slab
* @sk_timer: sock cleanup timer
* @sk_stamp: time stamp of last packet received
@@ -303,6 +304,9 @@ struct sock {
int sk_rcvbuf;
struct sk_filter __rcu *sk_filter;
+#ifdef CONFIG_UNIX_MULTICAST
+ struct sk_filter __rcu *sk_peer_filter;
+#endif
struct socket_wq __rcu *sk_wq;
#ifdef CONFIG_NET_DMA
@@ -587,6 +587,122 @@ void sk_filter_release_rcu(struct rcu_head *rcu)
}
EXPORT_SYMBOL(sk_filter_release_rcu);
+#ifdef CONFIG_UNIX_MULTICAST
+
+int __sk_attach_filter(struct sock_fprog *fprog, struct sock *sk, bool local)
+{
+ struct sk_filter *fp, *old_fp;
+ unsigned int fsize = sizeof(struct sock_filter) * fprog->len;
+ int err;
+
+ /* Make sure new filter is there and in the right amounts. */
+ if (fprog->filter == NULL)
+ return -EINVAL;
+
+ fp = sock_kmalloc(sk, fsize+sizeof(*fp), GFP_KERNEL);
+ if (!fp)
+ return -ENOMEM;
+ if (copy_from_user(fp->insns, fprog->filter, fsize)) {
+ sock_kfree_s(sk, fp, fsize+sizeof(*fp));
+ return -EFAULT;
+ }
+
+ atomic_set(&fp->refcnt, 1);
+ fp->len = fprog->len;
+ fp->bpf_func = sk_run_filter;
+
+ err = sk_chk_filter(fp->insns, fp->len);
+ if (err) {
+ sk_filter_uncharge(sk, fp);
+ return err;
+ }
+
+ bpf_jit_compile(fp);
+
+ if (local) {
+ old_fp = rcu_dereference_protected(sk->sk_filter,
+ sock_owned_by_user(sk));
+ rcu_assign_pointer(sk->sk_filter, fp);
+ } else {
+ old_fp = rcu_dereference_protected(sk->sk_peer_filter,
+ sock_owned_by_user(sk));
+ rcu_assign_pointer(sk->sk_peer_filter, fp);
+ }
+
+ if (old_fp)
+ sk_filter_uncharge(sk, old_fp);
+ return 0;
+}
+
+/**
+ * sk_attach_filter - attach a socket filter
+ * @fprog: the filter program
+ * @sk: the socket to use
+ *
+ * Attach the user's filter code. We first run some sanity checks on
+ * it to make sure it does not explode on us later. If an error
+ * occurs or there is insufficient memory for the filter a negative
+ * errno code is returned. On success the return is zero.
+ */
+int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
+{
+ return __sk_attach_filter(fprog, sk, true);
+}
+EXPORT_SYMBOL_GPL(sk_attach_filter);
+
+int __sk_detach_filter(struct sock *sk, bool local)
+{
+ int ret = -ENOENT;
+ struct sk_filter *filter;
+
+ if (local)
+ filter = rcu_dereference_protected(sk->sk_filter,
+ sock_owned_by_user(sk));
+ else
+ filter = rcu_dereference_protected(sk->sk_peer_filter,
+ sock_owned_by_user(sk));
+
+ if (filter) {
+ if (local)
+ RCU_INIT_POINTER(sk->sk_filter, NULL);
+ else
+ RCU_INIT_POINTER(sk->sk_peer_filter, NULL);
+ sk_filter_uncharge(sk, filter);
+ ret = 0;
+ }
+ return ret;
+}
+
+int sk_detach_filter(struct sock *sk)
+{
+ return __sk_detach_filter(sk, true);
+}
+EXPORT_SYMBOL_GPL(sk_detach_filter);
+
+/**
+ * sk_attach_peer_filter - attach a remote socket filter
+ * @fprog: the filter program
+ * @sk: the socket to use
+ *
+ * Attach the user's filter code. We first run some sanity checks on
+ * it to make sure it does not explode on us later. If an error
+ * occurs or there is insufficient memory for the filter a negative
+ * errno code is returned. On success the return is zero.
+ */
+int sk_attach_peer_filter(struct sock_fprog *fprog, struct sock *sk)
+{
+ return __sk_attach_filter(fprog, sk, false);
+}
+EXPORT_SYMBOL_GPL(sk_attach_peer_filter);
+
+int sk_detach_peer_filter(struct sock *sk)
+{
+ return __sk_detach_filter(sk, false);
+}
+EXPORT_SYMBOL_GPL(sk_detach_peer_filter);
+
+#else
+
/**
* sk_attach_filter - attach a socket filter
* @fprog: the filter program
@@ -652,3 +768,5 @@ int sk_detach_filter(struct sock *sk)
return ret;
}
EXPORT_SYMBOL_GPL(sk_detach_filter);
+
+#endif
@@ -121,6 +121,7 @@ DEFINE_SPINLOCK(unix_table_lock);
EXPORT_SYMBOL_GPL(unix_table_lock);
#ifdef CONFIG_UNIX_MULTICAST
#include <linux/sort.h>
+#include <linux/filter.h>
static DEFINE_SPINLOCK(unix_multicast_lock);
#endif
@@ -1767,8 +1768,10 @@ static int unix_dgram_sendmsg_multicast(struct sock_iocb *siocb,
restart:
for (i = others_set->offset ; i < others_set->cnt ; i++) {
struct sock *cur = others_set->items[i].s;
- unsigned int pkt_len;
+ unsigned int pkt_len = 0xffffffff;
+ unsigned int pkt_peer_len = 0xffffffff;
struct sk_filter *filter;
+ struct sk_filter *peer_filter;
if (!others_set->items[i].to_deliver)
continue;
@@ -1777,14 +1780,15 @@ restart:
BUG_ON(cur == NULL);
rcu_read_lock();
- filter = rcu_dereference(cur->sk_filter);
- if (filter)
- pkt_len = sk_run_filter(skb, filter->insns);
- else
- pkt_len = 0xffffffff;
+
+ peer_filter = rcu_dereference(cur->sk_peer_filter);
+
+ if (peer_filter)
+ pkt_peer_len = sk_run_filter(skb, peer_filter->insns);
+
rcu_read_unlock();
- if (pkt_len == 0) {
+ if (!pkt_peer_len) {
others_set->items[i].to_deliver = 0;
continue;
}
@@ -1795,13 +1799,33 @@ restart:
err = -ENOMEM;
goto out_free;
}
+
+ if (peer_filter)
+ pskb_trim(others_set->items[i].skb, pkt_peer_len);
+
+ rcu_read_lock();
+
+ filter = rcu_dereference(cur->sk_filter);
+
+ if (filter)
+ pkt_len = sk_run_filter(others_set->items[i].skb,
+ filter->insns);
+ rcu_read_unlock();
+
+ if (!pkt_len) {
+ others_set->items[i].to_deliver = 0;
+ continue;
+ }
+
+ if (filter)
+ pskb_trim(others_set->items[i].skb, pkt_len);
+
skb_set_owner_w(others_set->items[i].skb, sk);
err = unix_scm_to_skb(siocb->scm, others_set->items[i].skb,
true);
if (err < 0)
goto out_free;
unix_get_secdata(siocb->scm, others_set->items[i].skb);
- pskb_trim(others_set->items[i].skb, pkt_len);
}
for (i = others_set->offset ; i < others_set->cnt ; i++) {
@@ -2443,6 +2467,7 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
#ifdef CONFIG_UNIX_MULTICAST
struct unix_mreq mreq;
int err = 0;
+ struct sock *other = NULL;
if (level != SOL_UNIX)
return -ENOPROTOOPT;
@@ -2458,6 +2483,35 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
return -EFAULT;
break;
+ case UNIX_ATTACH_FILTER:
+ err = -EINVAL;
+ if (sock->sk->sk_type != SOCK_SEQPACKET)
+ return err;
+
+ if (!unix_sk(sock->sk)->mcast_group)
+ return err;
+
+ other = unix_peer_get(sock->sk);
+ if (!other)
+ return err;
+
+ BUG_ON(unix_sk(other)->mcast_group);
+
+ if (optlen == sizeof(struct sock_fprog)) {
+ struct sock_fprog fprog;
+
+ err = -EFAULT;
+ if (copy_from_user(&fprog, optval, sizeof(fprog))) {
+ sock_put(other);
+ return err;
+ }
+
+ err = sk_attach_peer_filter(&fprog, other);
+ }
+ sock_put(other);
+ return err;
+ break;
+
default:
break;
}
Multicast Unix domain sockets has an UNIX_ATTACH_FILTER setsockopt() operation to attach a Berkeley Packet Filter to a remote peer. This remote peer socket filter has to be a separate filter than the local one. So the peer cannot replace it. If local and peer filters are attached, both should be executed. First the peer BPF and then the local one. Filters can have state and this shouldn't be accesible to local filters. Signed-off-by: Javier Martinez Canillas <javier@collabora.co.uk> --- include/linux/filter.h | 4 ++ include/net/af_unix.h | 1 + include/net/sock.h | 4 ++ net/core/filter.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++ net/unix/af_unix.c | 70 +++++++++++++++++++++++++--- 5 files changed, 189 insertions(+), 8 deletions(-)