cmd/seccomp-profiler/disasm/disasm.go (181 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package disasm import ( "bufio" "fmt" "os" "regexp" "strconv" "strings" "github.com/elastic/go-seccomp-bpf/arch" ) const ( functionMarker = "TEXT" ) // Syscall is a system call found in the disassembly. type Syscall struct { Num int // Syscall number. Name string // Syscall name. Caller string // Function calling the syscall. Function string // Function used to make the systell (e.g. unix.Syscall6). Location string // File and line where the syscall is invoked. Assembly string // Assembly instruction that loads syscall number. } // ExtractSyscalls reads the objdump file and returns the syscalls that it // finds. func ExtractSyscalls(arch *arch.Info, objDump string) ([]Syscall, error) { var p *parser switch arch.ID { case i386Parser.ID: p = i386Parser case x86_64Parser.ID: p = x86_64Parser default: return nil, fmt.Errorf("unsupported architecture %v", arch.Name) } return p.Parse(objDump) } type parser struct { *arch.Info callOp string rawSyscallInstructions []string parse syscallParse } type syscallParse func(p *parser, line, caller string, instructions []string) (*Syscall, error) func (p *parser) Parse(objDump string) ([]Syscall, error) { f, err := os.Open(objDump) if err != nil { return nil, fmt.Errorf("failed to read objdump file: %v", err) } defer f.Close() var function string var instructions []string var syscalls []Syscall s := bufio.NewScanner(bufio.NewReader(f)) for s.Scan() { line := s.Text() instructions = append(instructions, line) // Find the start of a function. if strings.HasPrefix(line, functionMarker) { function = line[5:] instructions = instructions[:0] continue } syscall, err := p.parse(p, line, function, instructions) if err != nil { fmt.Fprintf(os.Stderr, "WARN: %v\n", err) continue } if syscall == nil { // Line was not a syscall. continue } // Found a syscall. Clear the instruction stack. instructions = instructions[:0] name, found := p.SyscallNumbers[syscall.Num] if !found { fmt.Fprintf(os.Stderr, "WARN: unknown syscall %d found at %+v\n", syscall.Num, syscall) continue } syscall.Caller = function syscall.Name = name syscalls = append(syscalls, *syscall) } if s.Err() != nil { return nil, err } return syscalls, nil } func (p *parser) isRawSyscall(line string) bool { for _, ins := range p.rawSyscallInstructions { if strings.Contains(line, ins) { return true } } return false } func (p *parser) isFunctionCall(line string) bool { return strings.Contains(line, p.callOp) } func isSyscallFunction(function string) bool { return strings.Contains(function, "syscall.Syscall(SB)") || strings.Contains(function, "syscall.Syscall6(SB)") || strings.Contains(function, "syscall.rawVforkSyscall(SB)") || strings.Contains(function, "syscall.RawSyscall(SB)") || strings.Contains(function, "syscall.RawSyscall6(SB)") || strings.Contains(function, "unix.RawSyscall(SB)") || strings.Contains(function, "unix.RawSyscall6(SB)") || strings.Contains(function, "unix.RawSyscallNoError(SB)") || strings.Contains(function, "unix.Syscall(SB)") || strings.Contains(function, "unix.Syscall6(SB)") || strings.Contains(function, "unix.Syscall9(SB)") || strings.Contains(function, "unix.SyscallNoError(SB)") } func findSyscallNum(instructions []string, syscall *Syscall, matchers ...*regexp.Regexp) error { for i := len(instructions) - 1; i >= 0; i-- { line := instructions[i] for _, regex := range matchers { matches := regex.FindStringSubmatch(line) if len(matches) != 2 { continue } num, err := strconv.ParseInt(matches[1], 0, 64) if err != nil { return fmt.Errorf("failed to parse syscall number %v: %v", matches[1], err) } syscall.Num = int(num) syscall.Assembly = matches[0] return nil } } return fmt.Errorf("assembly instruction for loading the syscall number was not found") } func lastInstruction(instructions []string) string { if len(instructions) >= 2 { return instructions[len(instructions)-2] } return "" } var ( x86_64Parser = &parser{ Info: arch.X86_64, rawSyscallInstructions: []string{"SYSCALL"}, callOp: "CALL", parse: parseX86_64, } i386Parser = &parser{ Info: arch.I386, rawSyscallInstructions: []string{"INT $0x80", "SYSENTER"}, callOp: "CALL", parse: parseX86_64, // i386 is similar to x86_64. } ) var ( // x86_64SyscallRegex matches the instruction to load the syscall number // into a register. x86_64SyscallRegex = regexp.MustCompile(`MOV[A-Z]? \$(.+), 0\(SP\)`) // x86_64RawSyscallRegex matches the instruction to load the syscall number // into a register for a raw "SYSCALL". x86_64RawSyscallRegex = regexp.MustCompile(`MOV[A-Z]? \$(.+), (?:AX|BP)`) ) func parseX86_64(p *parser, line, caller string, instructions []string) (*Syscall, error) { var m *regexp.Regexp if p.isRawSyscall(line) && !isSyscallFunction(caller) { m = x86_64RawSyscallRegex // Special case to handle a compiler optimization. This is a read // syscall found in cgo binaries. if inst := lastInstruction(instructions); inst != "" { if strings.Contains(inst, "XORL AX, AX") { fields := strings.Fields(line) return &Syscall{ Location: fields[0], Function: strings.Join(fields[3:], " "), Num: 0, Assembly: "XORL AX, AX", }, nil } } } else if p.isFunctionCall(line) && isSyscallFunction(line) { m = x86_64SyscallRegex } if m == nil { return nil, nil } fields := strings.Fields(line) s := &Syscall{ Location: fields[0], Function: strings.Join(fields[3:], " "), } if err := findSyscallNum(instructions, s, m); err != nil { return nil, fmt.Errorf("failed to extract syscall from '%v': %v", strings.TrimSpace(line), err) } return s, nil }