Skip to content

Extract eBPF disassembly assets for convenience #5802

Open
@FlorentRevest

Description

@FlorentRevest

When looking at a reproducer such as https://syzkaller.appspot.com/text?tag=ReproSyz&x=112d9a08e80000 the bpf$PROG_LOAD syscall is used with a [@ANYBLOB="18000000000000000000000000000000611200000000000095000000000000001383[...]"] argument that contains BPF bytecode.

It would be cool to extract the content of that buffer and pass it through objdump to extract the BPF instructions and upload them as assets to the dashboard, a bit like what we do for mounted file systems and fsck reports already.

This is not super high priority for me so I don't really intend to work on it but I wanted to write the idea down somewhere... :)

Also, here is a small poc that can be slapped into prog/bpf_disas_test.go. This is hacked together but goes as far as my curiosity could lead me:

// Copyright 2024 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package prog_test

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
	"testing"

	"github.com/google/syzkaller/prog"
	"github.com/google/syzkaller/sys/targets"
)

func Disassemble(insn []byte) (string, error) {
	tempFile, err := os.CreateTemp("", "*.bpf")
	if err != nil {
		return "", fmt.Errorf("failed to create temporary file: %w", err)
	}
	defer os.Remove(tempFile.Name())

	if _, err := tempFile.Write(insn); err != nil {
		return "", fmt.Errorf("failed to write bytecode to temporary file: %w", err)
	}

	if err := tempFile.Close(); err != nil {
		return "", fmt.Errorf("failed to close temporary file: %w", err)
	}

	cmd := exec.Command(
		"/usr/lib/bpf/bin/objdump", // Distributed by the binutils-bpf debian package
		"-D",
		"-b", "binary",
		"-m", "bpf",
		tempFile.Name(),
	)

	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("error running llvm-objdump: %w, output: %s", err, output)
	}

	// Strip off the header and last line
	lines := strings.Split(string(output), "\n")
	strippedOutput := ""
	if len(lines) > 7 {
		strippedOutput = strings.Join(lines[7:len(lines)-1], "\n")
	}

	return strippedOutput, nil
}

func TestBpfDisas(t *testing.T) {
	target, err := prog.GetTarget(targets.Linux, targets.AMD64)
	if err != nil {
		t.Fatal(err)
	}

	tests := []struct {
		prog  string
		disas string
	}{
		{
			`r0 = bpf$PROG_LOAD(0x5, &(0x7f0000000080)={0x9, 0x4, &(0x7f0000000700)=ANY=[@ANYBLOB="18000000000000000000000000000000611200000000000095000000000000001383096e16281fd43e588cf7a1e65f316e5e5600f1fb642cb352b9d4c50ae8366e5cadf97f4e52fdb37bdab01f9f6cc297b10500c98ea973fbaf38f9d47c5702c2bd9ebf0134b54dbee7458404277462d8ac80053e629d28aa5b25e324fd54d237d7921ff7b52f78ad9692619113594630a9eb6490c61332499f4861a57120ea351e61ca79b452a2bffd133c9ce1b4049b537a6310d0ee13db80ad6553ed19a04679d0d66bf61277501f370105113bd565ae2e766f9a79e314ecbc4000b4702ecfcaed9cb384edf20b1d3e7011bd384577a5a78efdd8687e0574465e490aa62e217fa49e4167d7edcd030c20937155d065ee7bb686bffcf28ec73d58a1d795c358c5aee99cae4c959ba2b9a78b4e231c46f8030523faf5b79ef84c5201a69d776df2041ae3d19a3d03fb1f2913fdd3fef24c94f1e224f872c1bebc0a7622231b2be88508a13a5b74e417cdad2076dd0ccdf44daf7404337f84783856b8582065669a46c1d570cdf4d6ce259d39fdc6bc4f066eb27ba18fc0110ebf3eb081d09b8587c911260c2ca2f49825e10b20733735ec2f4a80308c92dac2cac1608cbd739d385703e2933fda0dde43f3270d7170a7f5ce1dad0a2ae4691cc8487e113b89df89fd1d3c51723d79966e8c2eae12cf2dcfa7c09b15de3f494c5bfc35a8ac8124fb66066b2b3c7db6585b2fe802e86d2794d885c779de4ab1a0999fcedaea0b0497927b536e120212681673509f2aa7ad0875d5be6fa5f5812dd7966978f435924026737b78156906c3faf9e84f0cfb70a8d326262ce7ceadf4f95a7afb2bdf8250af753f32"], &(0x7f0000000100)='GPL\x00'}, 0x70)`,
			`   0:	18 00 00 00 00 00 00 00 	lddw %r0,0
   8:	00 00 00 00 00 00 00 00 
  10:	61 12 00 00 00 00 00 00 	ldxw %r1,[%r2+0]
  18:	95 00 00 00 00 00 00 00 	exit`,
		},
	}
	for i, test := range tests {
		t.Run(fmt.Sprint(i), func(t *testing.T) {
			p, err := target.Deserialize([]byte(test.prog), prog.NonStrict)
			if err != nil {
				t.Fatalf("failed to deserialize prog: %v", err)
			}
			structPtrArg := p.Calls[0].Args[1].(*prog.PointerArg)
			structGrpArg := structPtrArg.Res.(*prog.GroupArg)

			insnCntArg := structGrpArg.Inner[1].(*prog.ConstArg)
			insnCnt := int(insnCntArg.Val)

			insnPtrArg := structGrpArg.Inner[2].(*prog.PointerArg)
			insnGrpArg := insnPtrArg.Res.(*prog.GroupArg)
			insnUnionArg := insnGrpArg.Inner[0].(*prog.UnionArg)
			insnArg := insnUnionArg.Option.(*prog.DataArg)
			insn := insnArg.Data()[:insnCnt*8]

			disas, err := Disassemble(insn)

			if disas != test.disas {
				t.Fatalf("bad disas result:\n%v\nwant:\n%v", disas, test.disas)
			}
		})
	}
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions