bpf: add prog_digest and expose it via fdinfo/netlink
authorDaniel Borkmann <daniel@iogearbox.net>
Sun, 4 Dec 2016 22:19:41 +0000 (23:19 +0100)
committerDavid S. Miller <davem@davemloft.net>
Mon, 5 Dec 2016 20:33:11 +0000 (15:33 -0500)
When loading a BPF program via bpf(2), calculate the digest over
the program's instruction stream and store it in struct bpf_prog's
digest member. This is done at a point in time before any instructions
are rewritten by the verifier. Any unstable map file descriptor
number part of the imm field will be zeroed for the hash.

fdinfo example output for progs:

  # cat /proc/1590/fdinfo/5
  pos:          0
  flags:        02000002
  mnt_id:       11
  prog_type:    1
  prog_jited:   1
  prog_digest:  b27e8b06da22707513aa97363dfb11c7c3675d28
  memlock:      4096

When programs are pinned and retrieved by an ELF loader, the loader
can check the program's digest through fdinfo and compare it against
one that was generated over the ELF file's program section to see
if the program needs to be reloaded. Furthermore, this can also be
exposed through other means such as netlink in case of a tc cls/act
dump (or xdp in future), but also through tracepoints or other
facilities to identify the program. Other than that, the digest can
also serve as a base name for the work in progress kallsyms support
of programs. The digest doesn't depend/select the crypto layer, since
we need to keep dependencies to a minimum. iproute2 will get support
for this facility.

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/bpf.h
include/linux/filter.h
include/uapi/linux/pkt_cls.h
include/uapi/linux/tc_act/tc_bpf.h
kernel/bpf/core.c
kernel/bpf/syscall.c
kernel/bpf/verifier.c
net/sched/act_bpf.c
net/sched/cls_bpf.c

index 69d0a7f12a3bd516ef295c72153f7abcea56c0f3..8796ff03f4729f953d4a888163bf5b559225aa35 100644 (file)
@@ -216,6 +216,7 @@ u64 bpf_tail_call(u64 ctx, u64 r2, u64 index, u64 r4, u64 r5);
 u64 bpf_get_stackid(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5);
 
 bool bpf_prog_array_compatible(struct bpf_array *array, const struct bpf_prog *fp);
+void bpf_prog_calc_digest(struct bpf_prog *fp);
 
 const struct bpf_func_proto *bpf_get_trace_printk_proto(void);
 
index 97338134398f678bb96515df98960325fc09f85e..f078d2b1cff6a7c80f97cfb5fad7da966a64af86 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/workqueue.h>
 #include <linux/sched.h>
 #include <linux/capability.h>
+#include <linux/cryptohash.h>
 
 #include <net/sch_generic.h>
 
@@ -56,6 +57,9 @@ struct bpf_prog_aux;
 /* BPF program can access up to 512 bytes of stack space. */
 #define MAX_BPF_STACK  512
 
+/* Maximum BPF program size in bytes. */
+#define MAX_BPF_SIZE   (BPF_MAXINSNS * sizeof(struct bpf_insn))
+
 /* Helper macros for filter block array initializers. */
 
 /* ALU ops on registers, bpf_add|sub|...: dst_reg += src_reg */
@@ -404,8 +408,9 @@ struct bpf_prog {
                                cb_access:1,    /* Is control block accessed? */
                                dst_needed:1;   /* Do we need dst entry? */
        kmemcheck_bitfield_end(meta);
-       u32                     len;            /* Number of filter blocks */
        enum bpf_prog_type      type;           /* Type of BPF program */
+       u32                     len;            /* Number of filter blocks */
+       u32                     digest[SHA_DIGEST_WORDS]; /* Program digest */
        struct bpf_prog_aux     *aux;           /* Auxiliary fields */
        struct sock_fprog_kern  *orig_prog;     /* Original BPF program */
        unsigned int            (*bpf_func)(const void *ctx,
index 86786d45ee66de3b573c2d10a685782f00d700af..1adc0b654996c1cd88cadbbfc6487c46095a1a48 100644 (file)
@@ -397,6 +397,7 @@ enum {
        TCA_BPF_NAME,
        TCA_BPF_FLAGS,
        TCA_BPF_FLAGS_GEN,
+       TCA_BPF_DIGEST,
        __TCA_BPF_MAX,
 };
 
index 063d9d465119b2519f87aa4e31961311acf026e0..a6b88a6f7f712b271f07e809ee7a79697e6cb8cf 100644 (file)
@@ -27,6 +27,7 @@ enum {
        TCA_ACT_BPF_FD,
        TCA_ACT_BPF_NAME,
        TCA_ACT_BPF_PAD,
+       TCA_ACT_BPF_DIGEST,
        __TCA_ACT_BPF_MAX,
 };
 #define TCA_ACT_BPF_MAX (__TCA_ACT_BPF_MAX - 1)
index 82a04143368ef0a29fe6c9d8a3321d098d95c758..bdcc9f4ba7678a077c554c63963935a562796d5f 100644 (file)
@@ -136,6 +136,71 @@ void __bpf_prog_free(struct bpf_prog *fp)
        vfree(fp);
 }
 
+#define SHA_BPF_RAW_SIZE                                               \
+       round_up(MAX_BPF_SIZE + sizeof(__be64) + 1, SHA_MESSAGE_BYTES)
+
+/* Called under verifier mutex. */
+void bpf_prog_calc_digest(struct bpf_prog *fp)
+{
+       const u32 bits_offset = SHA_MESSAGE_BYTES - sizeof(__be64);
+       static u32 ws[SHA_WORKSPACE_WORDS];
+       static u8 raw[SHA_BPF_RAW_SIZE];
+       struct bpf_insn *dst = (void *)raw;
+       u32 i, bsize, psize, blocks;
+       bool was_ld_map;
+       u8 *todo = raw;
+       __be32 *result;
+       __be64 *bits;
+
+       sha_init(fp->digest);
+       memset(ws, 0, sizeof(ws));
+
+       /* We need to take out the map fd for the digest calculation
+        * since they are unstable from user space side.
+        */
+       for (i = 0, was_ld_map = false; i < fp->len; i++) {
+               dst[i] = fp->insnsi[i];
+               if (!was_ld_map &&
+                   dst[i].code == (BPF_LD | BPF_IMM | BPF_DW) &&
+                   dst[i].src_reg == BPF_PSEUDO_MAP_FD) {
+                       was_ld_map = true;
+                       dst[i].imm = 0;
+               } else if (was_ld_map &&
+                          dst[i].code == 0 &&
+                          dst[i].dst_reg == 0 &&
+                          dst[i].src_reg == 0 &&
+                          dst[i].off == 0) {
+                       was_ld_map = false;
+                       dst[i].imm = 0;
+               } else {
+                       was_ld_map = false;
+               }
+       }
+
+       psize = fp->len * sizeof(struct bpf_insn);
+       memset(&raw[psize], 0, sizeof(raw) - psize);
+       raw[psize++] = 0x80;
+
+       bsize  = round_up(psize, SHA_MESSAGE_BYTES);
+       blocks = bsize / SHA_MESSAGE_BYTES;
+       if (bsize - psize >= sizeof(__be64)) {
+               bits = (__be64 *)(todo + bsize - sizeof(__be64));
+       } else {
+               bits = (__be64 *)(todo + bsize + bits_offset);
+               blocks++;
+       }
+       *bits = cpu_to_be64((psize - 1) << 3);
+
+       while (blocks--) {
+               sha_transform(fp->digest, todo, ws);
+               todo += SHA_MESSAGE_BYTES;
+       }
+
+       result = (__force __be32 *)fp->digest;
+       for (i = 0; i < SHA_DIGEST_WORDS; i++)
+               result[i] = cpu_to_be32(fp->digest[i]);
+}
+
 static bool bpf_is_jmp_and_has_target(const struct bpf_insn *insn)
 {
        return BPF_CLASS(insn->code) == BPF_JMP  &&
index 85af86c496cdeed8bb1ce49db3295e0b199ae403..c0d2b423ce931e9a0e332fccd3c3e6bd78ba1b22 100644 (file)
@@ -662,8 +662,30 @@ static int bpf_prog_release(struct inode *inode, struct file *filp)
        return 0;
 }
 
+#ifdef CONFIG_PROC_FS
+static void bpf_prog_show_fdinfo(struct seq_file *m, struct file *filp)
+{
+       const struct bpf_prog *prog = filp->private_data;
+       char prog_digest[sizeof(prog->digest) * 2 + 1] = { };
+
+       bin2hex(prog_digest, prog->digest, sizeof(prog->digest));
+       seq_printf(m,
+                  "prog_type:\t%u\n"
+                  "prog_jited:\t%u\n"
+                  "prog_digest:\t%s\n"
+                  "memlock:\t%llu\n",
+                  prog->type,
+                  prog->jited,
+                  prog_digest,
+                  prog->pages * 1ULL << PAGE_SHIFT);
+}
+#endif
+
 static const struct file_operations bpf_prog_fops = {
-        .release = bpf_prog_release,
+#ifdef CONFIG_PROC_FS
+       .show_fdinfo    = bpf_prog_show_fdinfo,
+#endif
+       .release        = bpf_prog_release,
 };
 
 int bpf_prog_new_fd(struct bpf_prog *prog)
index 38d05da84a49508d80bee58c71f7c77011b6a243..cb37339ca0dac5f282ab3daca0a65a299479b7b9 100644 (file)
@@ -3176,6 +3176,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr)
                log_level = 0;
        }
 
+       bpf_prog_calc_digest(env->prog);
+
        ret = replace_map_fd_with_map_ptr(env);
        if (ret < 0)
                goto skip_full_check;
index 84c1d2da4f8b0576fde83e66ab0664d4e9d4e557..1c60317f01214ad77c17415bb2d7bc194b124615 100644 (file)
@@ -117,10 +117,19 @@ static int tcf_bpf_dump_bpf_info(const struct tcf_bpf *prog,
 static int tcf_bpf_dump_ebpf_info(const struct tcf_bpf *prog,
                                  struct sk_buff *skb)
 {
+       struct nlattr *nla;
+
        if (prog->bpf_name &&
            nla_put_string(skb, TCA_ACT_BPF_NAME, prog->bpf_name))
                return -EMSGSIZE;
 
+       nla = nla_reserve(skb, TCA_ACT_BPF_DIGEST,
+                         sizeof(prog->filter->digest));
+       if (nla == NULL)
+               return -EMSGSIZE;
+
+       memcpy(nla_data(nla), prog->filter->digest, nla_len(nla));
+
        return 0;
 }
 
index f70e03d2d2c8a99fc05167adf76715fa2c3dad90..adc776048d1a899f7955d0a92dd48b374178f3bb 100644 (file)
@@ -549,10 +549,18 @@ static int cls_bpf_dump_bpf_info(const struct cls_bpf_prog *prog,
 static int cls_bpf_dump_ebpf_info(const struct cls_bpf_prog *prog,
                                  struct sk_buff *skb)
 {
+       struct nlattr *nla;
+
        if (prog->bpf_name &&
            nla_put_string(skb, TCA_BPF_NAME, prog->bpf_name))
                return -EMSGSIZE;
 
+       nla = nla_reserve(skb, TCA_BPF_DIGEST, sizeof(prog->filter->digest));
+       if (nla == NULL)
+               return -EMSGSIZE;
+
+       memcpy(nla_data(nla), prog->filter->digest, nla_len(nla));
+
        return 0;
 }