Skip to content

Commit e45cc81

Browse files
committed
program: allow calculating the tag for a ProgramSpec
The kernel exposes a tag for each loaded program. This is just the truncated sha1 hash of the BPF bytecode. We can use this to check whether a loaded program corresponds to a ProgramSpec, for example to make sure that the latest version of a program is loaded. Add a function to asm.Instructions to calculate the tag and a helper to ProgramSpec to call the function using the native endianness.
1 parent da367ec commit e45cc81

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

Diff for: asm/instruction.go

+26
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package asm
22

33
import (
4+
"crypto/sha1"
45
"encoding/binary"
6+
"encoding/hex"
57
"errors"
68
"fmt"
79
"io"
810
"math"
911
"strings"
12+
13+
"github.com/cilium/ebpf/internal/unix"
1014
)
1115

1216
// InstructionSize is the size of a BPF instruction in bytes
@@ -159,6 +163,9 @@ func (ins *Instruction) mapOffset() uint32 {
159163
return uint32(uint64(ins.Constant) >> 32)
160164
}
161165

166+
// isLoadFromMap returns true if the instruction loads from a map.
167+
//
168+
// This covers both loading the map pointer and direct map value loads.
162169
func (ins *Instruction) isLoadFromMap() bool {
163170
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
164171
}
@@ -390,6 +397,25 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
390397
return nil
391398
}
392399

400+
// Tag calculates the kernel tag for a series of instructions.
401+
//
402+
// It mirrors bpf_prog_calc_tag in the kernel and so can be compared
403+
// to ProgramInfo.Tag to figure out whether a loaded program matches
404+
// certain instructions.
405+
func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) {
406+
h := sha1.New()
407+
for i, ins := range insns {
408+
if ins.isLoadFromMap() {
409+
ins.Constant = 0
410+
}
411+
_, err := ins.Marshal(h, bo)
412+
if err != nil {
413+
return "", fmt.Errorf("instruction %d: %w", i, err)
414+
}
415+
}
416+
return hex.EncodeToString(h.Sum(nil)[:unix.BPF_TAG_SIZE]), nil
417+
}
418+
393419
// Iterate allows iterating a BPF program while keeping track of
394420
// various offsets.
395421
//

Diff for: prog.go

+7
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ func (ps *ProgramSpec) Copy() *ProgramSpec {
8888
return &cpy
8989
}
9090

91+
// Tag calculates the kernel tag for a series of instructions.
92+
//
93+
// Use asm.Instructions.Tag if you need to calculate for non-native endianness.
94+
func (ps *ProgramSpec) Tag() (string, error) {
95+
return ps.Instructions.Tag(internal.NativeEndian)
96+
}
97+
9198
// Program represents BPF program loaded into the kernel.
9299
//
93100
// It is not safe to close a Program which is used by other goroutines.

Diff for: prog_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,43 @@ func TestProgramRejectIncorrectByteOrder(t *testing.T) {
478478
}
479479
}
480480

481+
func TestProgramSpecTag(t *testing.T) {
482+
arr := createArray(t)
483+
defer arr.Close()
484+
485+
spec := &ProgramSpec{
486+
Type: SocketFilter,
487+
Instructions: asm.Instructions{
488+
asm.LoadImm(asm.R0, -1, asm.DWord),
489+
asm.LoadMapPtr(asm.R1, arr.FD()),
490+
asm.Mov.Imm32(asm.R0, 0),
491+
asm.Return(),
492+
},
493+
License: "MIT",
494+
}
495+
496+
prog, err := NewProgram(spec)
497+
if err != nil {
498+
t.Fatal(err)
499+
}
500+
defer prog.Close()
501+
502+
info, err := prog.Info()
503+
testutils.SkipIfNotSupported(t, err)
504+
if err != nil {
505+
t.Fatal(err)
506+
}
507+
508+
tag, err := spec.Tag()
509+
if err != nil {
510+
t.Fatal("Can't calculate tag:", err)
511+
}
512+
513+
if tag != info.Tag {
514+
t.Errorf("Calculated tag %s doesn't match kernel tag %s", tag, info.Tag)
515+
}
516+
}
517+
481518
func TestProgramTypeLSM(t *testing.T) {
482519
prog, err := NewProgram(&ProgramSpec{
483520
AttachTo: "task_getpgid",
@@ -589,3 +626,24 @@ func ExampleProgram_unmarshalFromMap() {
589626
panic(err)
590627
}
591628
}
629+
630+
func ExampleProgramSpec_Tag() {
631+
spec := &ProgramSpec{
632+
Type: SocketFilter,
633+
Instructions: asm.Instructions{
634+
asm.LoadImm(asm.R0, 0, asm.DWord),
635+
asm.Return(),
636+
},
637+
License: "MIT",
638+
}
639+
640+
prog, _ := NewProgram(spec)
641+
info, _ := prog.Info()
642+
tag, _ := spec.Tag()
643+
644+
if info.Tag != tag {
645+
fmt.Printf("The tags don't match: %s != %s\n", info.Tag, tag)
646+
} else {
647+
fmt.Println("The programs are identical, tag is", tag)
648+
}
649+
}

0 commit comments

Comments
 (0)