Commit a3ea269b authored by Daniel Borkmann's avatar Daniel Borkmann Committed by David S. Miller

net: filter: keep original BPF program around

In order to open up the possibility to internally transform a BPF program
into an alternative and possibly non-trivial reversible representation, we
need to keep the original BPF program around, so that it can be passed back
to user space w/o the need of a complex decoder.

The reason for that use case resides in commit a8fc9277 ("sk-filter:
Add ability to get socket filter program (v2)"), that is, the ability
to retrieve the currently attached BPF filter from a given socket used
mainly by the checkpoint-restore project, for example.

Therefore, we add two helpers sk_{store,release}_orig_filter for taking
care of that. In the sk_unattached_filter_create() case, there's no such
possibility/requirement to retrieve a loaded BPF program. Therefore, we
can spare us the work in that case.

This approach will simplify and slightly speed up both, sk_get_filter()
and sock_diag_put_filterinfo() handlers as we won't need to successively
decode filters anymore through sk_decode_filter(). As we still need
sk_decode_filter() later on, we're keeping it around.

Joint work with Alexei Starovoitov.
Signed-off-by: default avatarAlexei Starovoitov <ast@plumgrid.com>
Signed-off-by: default avatarDaniel Borkmann <dborkman@redhat.com>
Cc: Pavel Emelyanov <xemul@parallels.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent f8bbbfc3
...@@ -19,14 +19,19 @@ struct compat_sock_fprog { ...@@ -19,14 +19,19 @@ struct compat_sock_fprog {
}; };
#endif #endif
struct sock_fprog_kern {
u16 len;
struct sock_filter *filter;
};
struct sk_buff; struct sk_buff;
struct sock; struct sock;
struct sk_filter struct sk_filter {
{
atomic_t refcnt; atomic_t refcnt;
u32 jited:1, /* Is our filter JIT'ed? */ u32 jited:1, /* Is our filter JIT'ed? */
len:31; /* Number of filter blocks */ len:31; /* Number of filter blocks */
struct sock_fprog_kern *orig_prog; /* Original BPF program */
struct rcu_head rcu; struct rcu_head rcu;
unsigned int (*bpf_func)(const struct sk_buff *skb, unsigned int (*bpf_func)(const struct sk_buff *skb,
const struct sock_filter *filter); const struct sock_filter *filter);
...@@ -42,14 +47,20 @@ static inline unsigned int sk_filter_size(unsigned int proglen) ...@@ -42,14 +47,20 @@ static inline unsigned int sk_filter_size(unsigned int proglen)
offsetof(struct sk_filter, insns[proglen])); offsetof(struct sk_filter, insns[proglen]));
} }
#define sk_filter_proglen(fprog) \
(fprog->len * sizeof(fprog->filter[0]))
extern int sk_filter(struct sock *sk, struct sk_buff *skb); extern int sk_filter(struct sock *sk, struct sk_buff *skb);
extern unsigned int sk_run_filter(const struct sk_buff *skb, extern unsigned int sk_run_filter(const struct sk_buff *skb,
const struct sock_filter *filter); const struct sock_filter *filter);
extern int sk_unattached_filter_create(struct sk_filter **pfp, extern int sk_unattached_filter_create(struct sk_filter **pfp,
struct sock_fprog *fprog); struct sock_fprog *fprog);
extern void sk_unattached_filter_destroy(struct sk_filter *fp); extern void sk_unattached_filter_destroy(struct sk_filter *fp);
extern int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk); extern int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk);
extern int sk_detach_filter(struct sock *sk); extern int sk_detach_filter(struct sock *sk);
extern int sk_chk_filter(struct sock_filter *filter, unsigned int flen); extern int sk_chk_filter(struct sock_filter *filter, unsigned int flen);
extern int sk_get_filter(struct sock *sk, struct sock_filter __user *filter, unsigned len); extern int sk_get_filter(struct sock *sk, struct sock_filter __user *filter, unsigned len);
extern void sk_decode_filter(struct sock_filter *filt, struct sock_filter *to); extern void sk_decode_filter(struct sock_filter *filt, struct sock_filter *to);
......
...@@ -629,6 +629,37 @@ int sk_chk_filter(struct sock_filter *filter, unsigned int flen) ...@@ -629,6 +629,37 @@ int sk_chk_filter(struct sock_filter *filter, unsigned int flen)
} }
EXPORT_SYMBOL(sk_chk_filter); EXPORT_SYMBOL(sk_chk_filter);
static int sk_store_orig_filter(struct sk_filter *fp,
const struct sock_fprog *fprog)
{
unsigned int fsize = sk_filter_proglen(fprog);
struct sock_fprog_kern *fkprog;
fp->orig_prog = kmalloc(sizeof(*fkprog), GFP_KERNEL);
if (!fp->orig_prog)
return -ENOMEM;
fkprog = fp->orig_prog;
fkprog->len = fprog->len;
fkprog->filter = kmemdup(fp->insns, fsize, GFP_KERNEL);
if (!fkprog->filter) {
kfree(fp->orig_prog);
return -ENOMEM;
}
return 0;
}
static void sk_release_orig_filter(struct sk_filter *fp)
{
struct sock_fprog_kern *fprog = fp->orig_prog;
if (fprog) {
kfree(fprog->filter);
kfree(fprog);
}
}
/** /**
* sk_filter_release_rcu - Release a socket filter by rcu_head * sk_filter_release_rcu - Release a socket filter by rcu_head
* @rcu: rcu_head that contains the sk_filter to free * @rcu: rcu_head that contains the sk_filter to free
...@@ -637,6 +668,7 @@ void sk_filter_release_rcu(struct rcu_head *rcu) ...@@ -637,6 +668,7 @@ void sk_filter_release_rcu(struct rcu_head *rcu)
{ {
struct sk_filter *fp = container_of(rcu, struct sk_filter, rcu); struct sk_filter *fp = container_of(rcu, struct sk_filter, rcu);
sk_release_orig_filter(fp);
bpf_jit_free(fp); bpf_jit_free(fp);
} }
EXPORT_SYMBOL(sk_filter_release_rcu); EXPORT_SYMBOL(sk_filter_release_rcu);
...@@ -669,8 +701,8 @@ static int __sk_prepare_filter(struct sk_filter *fp) ...@@ -669,8 +701,8 @@ static int __sk_prepare_filter(struct sk_filter *fp)
int sk_unattached_filter_create(struct sk_filter **pfp, int sk_unattached_filter_create(struct sk_filter **pfp,
struct sock_fprog *fprog) struct sock_fprog *fprog)
{ {
unsigned int fsize = sk_filter_proglen(fprog);
struct sk_filter *fp; struct sk_filter *fp;
unsigned int fsize = sizeof(struct sock_filter) * fprog->len;
int err; int err;
/* Make sure new filter is there and in the right amounts. */ /* Make sure new filter is there and in the right amounts. */
...@@ -680,10 +712,16 @@ int sk_unattached_filter_create(struct sk_filter **pfp, ...@@ -680,10 +712,16 @@ int sk_unattached_filter_create(struct sk_filter **pfp,
fp = kmalloc(sk_filter_size(fprog->len), GFP_KERNEL); fp = kmalloc(sk_filter_size(fprog->len), GFP_KERNEL);
if (!fp) if (!fp)
return -ENOMEM; return -ENOMEM;
memcpy(fp->insns, fprog->filter, fsize); memcpy(fp->insns, fprog->filter, fsize);
atomic_set(&fp->refcnt, 1); atomic_set(&fp->refcnt, 1);
fp->len = fprog->len; fp->len = fprog->len;
/* Since unattached filters are not copied back to user
* space through sk_get_filter(), we do not need to hold
* a copy here, and can spare us the work.
*/
fp->orig_prog = NULL;
err = __sk_prepare_filter(fp); err = __sk_prepare_filter(fp);
if (err) if (err)
...@@ -716,7 +754,7 @@ EXPORT_SYMBOL_GPL(sk_unattached_filter_destroy); ...@@ -716,7 +754,7 @@ EXPORT_SYMBOL_GPL(sk_unattached_filter_destroy);
int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk) int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
{ {
struct sk_filter *fp, *old_fp; struct sk_filter *fp, *old_fp;
unsigned int fsize = sizeof(struct sock_filter) * fprog->len; unsigned int fsize = sk_filter_proglen(fprog);
unsigned int sk_fsize = sk_filter_size(fprog->len); unsigned int sk_fsize = sk_filter_size(fprog->len);
int err; int err;
...@@ -730,6 +768,7 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk) ...@@ -730,6 +768,7 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
fp = sock_kmalloc(sk, sk_fsize, GFP_KERNEL); fp = sock_kmalloc(sk, sk_fsize, GFP_KERNEL);
if (!fp) if (!fp)
return -ENOMEM; return -ENOMEM;
if (copy_from_user(fp->insns, fprog->filter, fsize)) { if (copy_from_user(fp->insns, fprog->filter, fsize)) {
sock_kfree_s(sk, fp, sk_fsize); sock_kfree_s(sk, fp, sk_fsize);
return -EFAULT; return -EFAULT;
...@@ -738,6 +777,12 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk) ...@@ -738,6 +777,12 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
atomic_set(&fp->refcnt, 1); atomic_set(&fp->refcnt, 1);
fp->len = fprog->len; fp->len = fprog->len;
err = sk_store_orig_filter(fp, fprog);
if (err) {
sk_filter_uncharge(sk, fp);
return -ENOMEM;
}
err = __sk_prepare_filter(fp); err = __sk_prepare_filter(fp);
if (err) { if (err) {
sk_filter_uncharge(sk, fp); sk_filter_uncharge(sk, fp);
...@@ -750,6 +795,7 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk) ...@@ -750,6 +795,7 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
if (old_fp) if (old_fp)
sk_filter_uncharge(sk, old_fp); sk_filter_uncharge(sk, old_fp);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(sk_attach_filter); EXPORT_SYMBOL_GPL(sk_attach_filter);
...@@ -769,6 +815,7 @@ int sk_detach_filter(struct sock *sk) ...@@ -769,6 +815,7 @@ int sk_detach_filter(struct sock *sk)
sk_filter_uncharge(sk, filter); sk_filter_uncharge(sk, filter);
ret = 0; ret = 0;
} }
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(sk_detach_filter); EXPORT_SYMBOL_GPL(sk_detach_filter);
...@@ -851,34 +898,41 @@ void sk_decode_filter(struct sock_filter *filt, struct sock_filter *to) ...@@ -851,34 +898,41 @@ void sk_decode_filter(struct sock_filter *filt, struct sock_filter *to)
to->k = filt->k; to->k = filt->k;
} }
int sk_get_filter(struct sock *sk, struct sock_filter __user *ubuf, unsigned int len) int sk_get_filter(struct sock *sk, struct sock_filter __user *ubuf,
unsigned int len)
{ {
struct sock_fprog_kern *fprog;
struct sk_filter *filter; struct sk_filter *filter;
int i, ret; int ret = 0;
lock_sock(sk); lock_sock(sk);
filter = rcu_dereference_protected(sk->sk_filter, filter = rcu_dereference_protected(sk->sk_filter,
sock_owned_by_user(sk)); sock_owned_by_user(sk));
ret = 0;
if (!filter) if (!filter)
goto out; goto out;
ret = filter->len;
/* We're copying the filter that has been originally attached,
* so no conversion/decode needed anymore.
*/
fprog = filter->orig_prog;
ret = fprog->len;
if (!len) if (!len)
/* User space only enquires number of filter blocks. */
goto out; goto out;
ret = -EINVAL; ret = -EINVAL;
if (len < filter->len) if (len < fprog->len)
goto out; goto out;
ret = -EFAULT; ret = -EFAULT;
for (i = 0; i < filter->len; i++) { if (copy_to_user(ubuf, fprog->filter, sk_filter_proglen(fprog)))
struct sock_filter fb;
sk_decode_filter(&filter->insns[i], &fb);
if (copy_to_user(&ubuf[i], &fb, sizeof(fb)))
goto out; goto out;
}
ret = filter->len; /* Instead of bytes, the API requests to return the number
* of filter blocks.
*/
ret = fprog->len;
out: out:
release_sock(sk); release_sock(sk);
return ret; return ret;
......
...@@ -52,9 +52,10 @@ EXPORT_SYMBOL_GPL(sock_diag_put_meminfo); ...@@ -52,9 +52,10 @@ EXPORT_SYMBOL_GPL(sock_diag_put_meminfo);
int sock_diag_put_filterinfo(struct user_namespace *user_ns, struct sock *sk, int sock_diag_put_filterinfo(struct user_namespace *user_ns, struct sock *sk,
struct sk_buff *skb, int attrtype) struct sk_buff *skb, int attrtype)
{ {
struct nlattr *attr; struct sock_fprog_kern *fprog;
struct sk_filter *filter; struct sk_filter *filter;
unsigned int len; struct nlattr *attr;
unsigned int flen;
int err = 0; int err = 0;
if (!ns_capable(user_ns, CAP_NET_ADMIN)) { if (!ns_capable(user_ns, CAP_NET_ADMIN)) {
...@@ -63,24 +64,20 @@ int sock_diag_put_filterinfo(struct user_namespace *user_ns, struct sock *sk, ...@@ -63,24 +64,20 @@ int sock_diag_put_filterinfo(struct user_namespace *user_ns, struct sock *sk,
} }
rcu_read_lock(); rcu_read_lock();
filter = rcu_dereference(sk->sk_filter); filter = rcu_dereference(sk->sk_filter);
len = filter ? filter->len * sizeof(struct sock_filter) : 0; if (!filter)
goto out;
attr = nla_reserve(skb, attrtype, len); fprog = filter->orig_prog;
flen = sk_filter_proglen(fprog);
attr = nla_reserve(skb, attrtype, flen);
if (attr == NULL) { if (attr == NULL) {
err = -EMSGSIZE; err = -EMSGSIZE;
goto out; goto out;
} }
if (filter) { memcpy(nla_data(attr), fprog->filter, flen);
struct sock_filter *fb = (struct sock_filter *)nla_data(attr);
int i;
for (i = 0; i < filter->len; i++, fb++)
sk_decode_filter(&filter->insns[i], fb);
}
out: out:
rcu_read_unlock(); rcu_read_unlock();
return err; return err;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment