[PATCH v2 11/13] bpftool: Add support for signing BPF programs

KP Singh kpsingh at kernel.org
Thu Jul 24 17:07:33 UTC 2025


On Tue, Jul 22, 2025 at 5:51 PM Quentin Monnet <qmo at kernel.org> wrote:
>
> 2025-07-21 23:19 UTC+0200 ~ KP Singh <kpsingh at kernel.org>
> > Two modes of operation being added:
> >
> > Add two modes of operation:
> >
> > * For prog load, allow signing a program immediately before loading. This
> >   is essential for command-line testing and administration.
> >
> >       bpftool prog load -S -k <private_key> -i <identity_cert> fentry_test.bpf.o
> >
> > * For gen skeleton, embed a pre-generated signature into the C skeleton
> >   file. This supports the use of signed programs in compiled applications.
> >
> >       bpftool gen skeleton -S -k <private_key> -i <identity_cert> fentry_test.bpf.o
> >
> > Generation of the loader program and its metadata map is implemented in
> > libbpf (bpf_obj__gen_loader). bpftool generates a skeleton that loads
> > the program and automates the required steps: freezing the map, creating
> > an exclusive map, loading, and running. Users can use standard libbpf
> > APIs directly or integrate loader program generation into their own
> > toolchains.
>
>
> Thanks KP! Some bpftool-related comments below. Looks good overall, I
> mostly have minor comments.
>
> One concern might be the license for the new file, GPL-2.0 in your
> patch, whereas bpftool is dual-licensed. I hope this is simply an oversight?

An oversight, fixed.

>
>
> >
> > Signed-off-by: KP Singh <kpsingh at kernel.org>
> > ---
> >  .../bpf/bpftool/Documentation/bpftool-gen.rst |  12 +
> >  .../bpftool/Documentation/bpftool-prog.rst    |  12 +
> >  tools/bpf/bpftool/Makefile                    |   6 +-
> >  tools/bpf/bpftool/cgroup.c                    |   5 +-
> >  tools/bpf/bpftool/gen.c                       |  58 ++++-
> >  tools/bpf/bpftool/main.c                      |  21 +-
> >  tools/bpf/bpftool/main.h                      |  11 +
> >  tools/bpf/bpftool/prog.c                      |  25 +++
> >  tools/bpf/bpftool/sign.c                      | 210 ++++++++++++++++++
> >  9 files changed, 352 insertions(+), 8 deletions(-)
> >  create mode 100644 tools/bpf/bpftool/sign.c
> >
> > diff --git a/tools/bpf/bpftool/Documentation/bpftool-gen.rst b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> > index ca860fd97d8d..2997313003b1 100644
> > --- a/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> > +++ b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> > @@ -185,6 +185,18 @@ OPTIONS
> >      For skeletons, generate a "light" skeleton (also known as "loader"
> >      skeleton). A light skeleton contains a loader eBPF program. It does not use
> >      the majority of the libbpf infrastructure, and does not need libelf.
>
>
> Blank line separator, please

done

>
>
> > +-S, --sign
> > +    For skeletons, generate a signed skeleton. This option must be used with
> > +    **-k** and **-i**. Using this flag implicitly enables **--use-loader**.
> > +    See the "Signed Skeletons" section in the description of the
> > +    **gen skeleton** command for more details.
> > +
> > +-k <private_key.pem>
> > +    Path to the private key file in PEM format, required for signing.
> > +
> > +-i <certificate.x509>
> > +    Path to the X.509 certificate file in PEM or DER format, required for
> > +    signing.
>
>
> Please also update the options list in the SYNOPSIS section at the top
> of the page; and the option list at the bottom of gen.c (just like for
> "--use-loader").

done also, isn't this the right formatting for the SYNOPSIS given that
some of these are optional?

**bpftool** [*OPTIONS*] **prog** *COMMAND*
*OPTIONS* := { |COMMON_OPTIONS| [ { **-f** | **--bpffs** } ] [ {
**-m** | **--mapcompat** } ]
[ { **-n** | **--nomount** } ] [ { **-L** | **--use-loader** } ]
[ { { **-S** | **--sign** } **-k** <private_key.pem> **-i**
<certificate.x509> } ] }

not an expert here but I vaguely remember.

Also do you think we need to:

                { "use-loader", no_argument,    NULL,   'L' },
-               { "sign",       required_argument, NULL, 'S'},
+               { "sign",       no_argument,    NULL,   'S' },


Now that we don't use an argument blob for --sign?


>
> Can you also please take a look at the bash completion update? It
> shouldn't be too hard if you look at how it deals with other options, in
> particular --base-btf that also takes one argument - and I can help if
> necessary.

I will give it a go.

>
>
> >
> >  EXAMPLES
> >  ========
> > diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> > index f69fd92df8d8..dc2ca196137e 100644
> > --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> > +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> > @@ -248,6 +248,18 @@ OPTIONS
> >      creating the maps, and loading the programs (see **bpftool prog tracelog**
> >      as a way to dump those messages).
> >
> > +-S, --sign
> > +    Enable signing of the BPF program before loading. This option must be
> > +    used with **-k** and **-i**. Using this flag implicitly enables
> > +    **--use-loader**.
> > +
> > +-k <private_key.pem>
> > +    Path to the private key file in PEM format, required when signing.
> > +
> > +-i <certificate.x509>
> > +    Path to the X.509 certificate file in PEM or DER format, required when
> > +    signing.
>
>
> Same as for skeletons: please update the list of options in the synopsis
> and at the bottom of prog.c (bash completion for skeletons' options
> should also cover this case, so no additional work required here).
>
>
> > +
> >  EXAMPLES
> >  ========
> >  **# bpftool prog show**
> > diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile
> > index 9e9a5f006cd2..586d1b2595d1 100644
> > --- a/tools/bpf/bpftool/Makefile
> > +++ b/tools/bpf/bpftool/Makefile
> > @@ -130,8 +130,8 @@ include $(FEATURES_DUMP)
> >  endif
> >  endif
> >
> > -LIBS = $(LIBBPF) -lelf -lz
> > -LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz
> > +LIBS = $(LIBBPF) -lelf -lz -lcrypto
> > +LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz -lcrypto
> >
> >  ifeq ($(feature-libelf-zstd),1)
> >  LIBS += -lzstd
> > @@ -194,7 +194,7 @@ endif
> >
> >  BPFTOOL_BOOTSTRAP := $(BOOTSTRAP_OUTPUT)bpftool
> >
> > -BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o)
> > +BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o sign.o)
> >  $(BOOTSTRAP_OBJS): $(LIBBPF_BOOTSTRAP)
> >
> >  OBJS = $(patsubst %.c,$(OUTPUT)%.o,$(SRCS)) $(OUTPUT)disasm.o
> > diff --git a/tools/bpf/bpftool/cgroup.c b/tools/bpf/bpftool/cgroup.c
> > index 944ebe21a216..90c9aa297806 100644
> > --- a/tools/bpf/bpftool/cgroup.c
> > +++ b/tools/bpf/bpftool/cgroup.c
> > @@ -1,7 +1,10 @@
> >  // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >  // Copyright (C) 2017 Facebook
> >  // Author: Roman Gushchin <guro at fb.com>
> > -
>
>
> Let's keep the blank line

Done

>
>
> > +#undef GCC_VERSION
> > +#ifndef _GNU_SOURCE
> > +#define _GNU_SOURCE
> > +#endif
>
>
> What are these for?

kpsingh at kpsingh-genoa:~/projects/linux/tools/bpf/bpftool$ vmk

Auto-detecting system features:
...                         clang-bpf-co-re: [ on  ]
...                                    llvm: [ on  ]
...                                  libcap: [ on  ]
...                                  libbfd: [ OFF ]

In file included from cgroup.c:19:
In file included from ./main.h:16:
/home/kpsingh/projects/linux/tools/bpf/bpftool/libbpf/include/bpf/skel_internal.h:87:9:
error: call to undeclared function 'syscall'; ISO C99 and later do not
support implicit function declarations
[-Wimplicit-function-declaration]
   87 |         return syscall(__NR_bpf, cmd, attr, size);
      |                ^
1 error generated.

>
>
> >  #define _XOPEN_SOURCE 500
> >  #include <errno.h>
> >  #include <fcntl.h>
>
> [...]
>
> > diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
> > index 2b7f2bd3a7db..fc25bb390ec7 100644
> > --- a/tools/bpf/bpftool/main.c
> > +++ b/tools/bpf/bpftool/main.c
> > @@ -33,6 +33,9 @@ bool relaxed_maps;
> >  bool use_loader;
> >  struct btf *base_btf;
> >  struct hashmap *refs_table;
> > +bool sign_progs;
> > +const char *private_key_path;
> > +const char *cert_path;
> >
> >  static void __noreturn clean_and_exit(int i)
> >  {
> > @@ -447,6 +450,7 @@ int main(int argc, char **argv)
> >               { "nomount",    no_argument,    NULL,   'n' },
> >               { "debug",      no_argument,    NULL,   'd' },
> >               { "use-loader", no_argument,    NULL,   'L' },
> > +             { "sign",       required_argument, NULL, 'S'},
> >               { "base-btf",   required_argument, NULL, 'B' },
> >               { 0 }
> >       };
> > @@ -473,7 +477,7 @@ int main(int argc, char **argv)
> >       bin_name = "bpftool";
> >
> >       opterr = 0;
> > -     while ((opt = getopt_long(argc, argv, "VhpjfLmndB:l",
> > +     while ((opt = getopt_long(argc, argv, "VhpjfLmndSi:k:B:l",
> >                                 options, NULL)) >= 0) {
> >               switch (opt) {
> >               case 'V':
> > @@ -519,6 +523,16 @@ int main(int argc, char **argv)
> >               case 'L':
> >                       use_loader = true;
> >                       break;
> > +             case 'S':
> > +                     sign_progs = true;
> > +                     use_loader = true;
> > +                     break;
> > +             case 'k':
> > +                     private_key_path = optarg;
> > +                     break;
> > +             case 'i':
> > +                     cert_path = optarg;
> > +                     break;
> >               default:
> >                       p_err("unrecognized option '%s'", argv[optind - 1]);
> >                       if (json_output)
> > @@ -533,6 +547,11 @@ int main(int argc, char **argv)
> >       if (argc < 0)
> >               usage();
> >
> > +     if (sign_progs && (private_key_path == NULL || cert_path == NULL)) {
> > +             p_err("-i <identity_x509_cert> and -k <private> key must be supplied with -S for signing");
> > +             return -EINVAL;
> > +     }
>
>
> What if -i and/or -k are passed without -S?

We can either print a warning or error out

A) User does not want to sign removes --sign and forgets to remove -i
-k (better with warning)
B) User wants to sign but forgets to --sign (better with error)

I'd say we print an error so that we don't accidentally not sign, WDYT?

The reason why I think we should keep an explicit --sign is because we
can also extend this to have e.g. --verify.

- KP

>
>
> > +
> >       if (version_requested)
> >               ret = do_version(argc, argv);
> >       else
> > diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
> > index 6db704fda5c0..f921af3cda87 100644
> > --- a/tools/bpf/bpftool/main.h
> > +++ b/tools/bpf/bpftool/main.h
> > @@ -6,9 +6,14 @@
> >
> >  /* BFD and kernel.h both define GCC_VERSION, differently */
> >  #undef GCC_VERSION
> > +#ifndef _GNU_SOURCE
> > +#define _GNU_SOURCE
> > +#endif
> >  #include <stdbool.h>
> >  #include <stdio.h>
> > +#include <errno.h>
> >  #include <stdlib.h>
> > +#include <bpf/skel_internal.h>
>
>
> Wnat do you need these includes (and _GNU_SOURCE) in main.h for?

Explained above, let me know if you have better ideas on where to place these.

>
>
> >  #include <linux/bpf.h>
> >  #include <linux/compiler.h>
> >  #include <linux/kernel.h>
>
> [...]
>
> > diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
> > new file mode 100644
> > index 000000000000..f0b5dd10a46b
> > --- /dev/null
> > +++ b/tools/bpf/bpftool/sign.c
> > @@ -0,0 +1,210 @@
> > +// SPDX-License-Identifier: GPL-2.0
>
>
> Please consider making this file dual-licensed like the rest of
> bpftool's source code, "(GPL-2.0-only OR BSD-2-Clause)".

Done.

>
>
> > +
> > +/*
> > + * Copyright (C) 2022 Google LLC.
>
>
> 2025?

Let's keep it 2022, nah just kidding :) Thanks.

>
>
> > + */
> > +#define _GNU_SOURCE
>
>
> Please guard this:
>
>         #ifndef _GNU_SOURCE
>         #define _GNU_SOURCE
>         #endif
>
> This is because "llvm-config --cflags" passes -D_GNU_SOURCE and we may
> end up with a duplicate definition, otherwise.

ack, done.

>
>
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <stdint.h>
> > +#include <stdbool.h>
> > +#include <string.h>
> > +#include <string.h>
> > +#include <getopt.h>
> > +#include <err.h>
> > +#include <openssl/opensslv.h>
> > +#include <openssl/bio.h>
> > +#include <openssl/evp.h>
> > +#include <openssl/pem.h>
> > +#include <openssl/err.h>
> > +#include <openssl/cms.h>
> > +#include <linux/keyctl.h>
> > +#include <errno.h>
> > +
> > +#include <bpf/skel_internal.h>
> > +
> > +#include "main.h"
> > +
> > +#define OPEN_SSL_ERR_BUF_LEN 256
> > +
> > +static void display_openssl_errors(int l)
> > +{
> > +     char buf[OPEN_SSL_ERR_BUF_LEN];
> > +     const char *file;
> > +     const char *data;
> > +     unsigned long e;
> > +     int flags;
> > +     int line;
> > +
> > +     while ((e = ERR_get_error_all(&file, &line, NULL, &data, &flags))) {
> > +             ERR_error_string_n(e, buf, sizeof(buf));
> > +             if (data && (flags & ERR_TXT_STRING)) {
> > +                     p_err("OpenSSL %s: %s:%d: %s\n", buf, file, line, data);
>
>
> Please remove the trailing '\n', p_err() handles it already.
>
>
> > +             } else {
> > +                     p_err("OpenSSL %s: %s:%d\n", buf, file, line);
>
>
> Same here.

done.

- KP

>
> [...]



More information about the Linux-security-module-archive mailing list