Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net
[sfrench/cifs-2.6.git] / kernel / bpf / cgroup.c
index 4d76f16524cc445e25792de8f6829b6c1fed3302..ac53102e244a7a5db7385389daf4aae0dd9293d0 100644 (file)
@@ -1276,16 +1276,23 @@ static bool __cgroup_bpf_prog_array_is_empty(struct cgroup *cgrp,
 
 static int sockopt_alloc_buf(struct bpf_sockopt_kern *ctx, int max_optlen)
 {
-       if (unlikely(max_optlen > PAGE_SIZE) || max_optlen < 0)
+       if (unlikely(max_optlen < 0))
                return -EINVAL;
 
+       if (unlikely(max_optlen > PAGE_SIZE)) {
+               /* We don't expose optvals that are greater than PAGE_SIZE
+                * to the BPF program.
+                */
+               max_optlen = PAGE_SIZE;
+       }
+
        ctx->optval = kzalloc(max_optlen, GFP_USER);
        if (!ctx->optval)
                return -ENOMEM;
 
        ctx->optval_end = ctx->optval + max_optlen;
 
-       return 0;
+       return max_optlen;
 }
 
 static void sockopt_free_buf(struct bpf_sockopt_kern *ctx)
@@ -1319,13 +1326,13 @@ int __cgroup_bpf_run_filter_setsockopt(struct sock *sk, int *level,
         */
        max_optlen = max_t(int, 16, *optlen);
 
-       ret = sockopt_alloc_buf(&ctx, max_optlen);
-       if (ret)
-               return ret;
+       max_optlen = sockopt_alloc_buf(&ctx, max_optlen);
+       if (max_optlen < 0)
+               return max_optlen;
 
        ctx.optlen = *optlen;
 
-       if (copy_from_user(ctx.optval, optval, *optlen) != 0) {
+       if (copy_from_user(ctx.optval, optval, min(*optlen, max_optlen)) != 0) {
                ret = -EFAULT;
                goto out;
        }
@@ -1353,8 +1360,14 @@ int __cgroup_bpf_run_filter_setsockopt(struct sock *sk, int *level,
                /* export any potential modifications */
                *level = ctx.level;
                *optname = ctx.optname;
-               *optlen = ctx.optlen;
-               *kernel_optval = ctx.optval;
+
+               /* optlen == 0 from BPF indicates that we should
+                * use original userspace data.
+                */
+               if (ctx.optlen != 0) {
+                       *optlen = ctx.optlen;
+                       *kernel_optval = ctx.optval;
+               }
        }
 
 out:
@@ -1385,12 +1398,12 @@ int __cgroup_bpf_run_filter_getsockopt(struct sock *sk, int level,
            __cgroup_bpf_prog_array_is_empty(cgrp, BPF_CGROUP_GETSOCKOPT))
                return retval;
 
-       ret = sockopt_alloc_buf(&ctx, max_optlen);
-       if (ret)
-               return ret;
-
        ctx.optlen = max_optlen;
 
+       max_optlen = sockopt_alloc_buf(&ctx, max_optlen);
+       if (max_optlen < 0)
+               return max_optlen;
+
        if (!retval) {
                /* If kernel getsockopt finished successfully,
                 * copy whatever was returned to the user back
@@ -1404,10 +1417,8 @@ int __cgroup_bpf_run_filter_getsockopt(struct sock *sk, int level,
                        goto out;
                }
 
-               if (ctx.optlen > max_optlen)
-                       ctx.optlen = max_optlen;
-
-               if (copy_from_user(ctx.optval, optval, ctx.optlen) != 0) {
+               if (copy_from_user(ctx.optval, optval,
+                                  min(ctx.optlen, max_optlen)) != 0) {
                        ret = -EFAULT;
                        goto out;
                }
@@ -1436,10 +1447,12 @@ int __cgroup_bpf_run_filter_getsockopt(struct sock *sk, int level,
                goto out;
        }
 
-       if (copy_to_user(optval, ctx.optval, ctx.optlen) ||
-           put_user(ctx.optlen, optlen)) {
-               ret = -EFAULT;
-               goto out;
+       if (ctx.optlen != 0) {
+               if (copy_to_user(optval, ctx.optval, ctx.optlen) ||
+                   put_user(ctx.optlen, optlen)) {
+                       ret = -EFAULT;
+                       goto out;
+               }
        }
 
        ret = ctx.retval;