assembler.go (166 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 seccomp
import (
"encoding/binary"
"fmt"
"math"
"unsafe"
"golang.org/x/net/bpf"
)
const (
argumentOffset = uint32(16)
sizeOfUint32 = int(unsafe.Sizeof(uint32(0)))
sizeOfUint64 = uint32(unsafe.Sizeof(uint64(0)))
)
var nativeEndian binary.ByteOrder
func init() {
buf := [2]byte{}
*(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
switch buf {
case [2]byte{0xCD, 0xAB}:
nativeEndian = binary.LittleEndian
case [2]byte{0xAB, 0xCD}:
nativeEndian = binary.BigEndian
default:
panic("Could not determine native endianness.")
}
}
// Label marks a jump destination in the instruction list of the Program.
type Label int
// Index is the concrete index of an instruction in the instruction list.
type Index int
// JumpIf jumps conditionally to the true or the false label.
// The concrete condition is not relevant to resolve the jumps.
type JumpIf struct {
index Index
trueLabel Label
falseLabel Label
}
// The Program consists of a list of bpf.Instructions.
// Conditional jumps can point to different labels in the program and must be resolved by calling ResolveJumps.
//
// NewLabel creates a new label that can be used as jump destination.
//
// SetLabel must be used to specify the concrete instruction.
// Only forward jumps are supported; this means a label must not be used after setting it.
type Program struct {
instructions []bpf.Instruction
jumps []JumpIf
labels map[Label][]Index
nextLabel Label
}
// NewProgram returns an initialized empty program.
func NewProgram() Program {
return Program{
labels: make(map[Label][]Index),
nextLabel: Label(1),
}
}
// JmpIfTrue inserts a conditional jump.
// If the condition is true, it jumps to the given label.
// If it is false, the program flow continues with the next instruction.
func (p *Program) JmpIfTrue(cond bpf.JumpTest, val uint32, trueLabel Label) {
nextInst := p.NewLabel()
p.JmpIf(cond, val, trueLabel, nextInst)
p.SetLabel(nextInst)
}
// JmpIf inserts a conditional jump.
// If the condition is true, it jumps to the true label.
// If it is false, it jumps to the false label.
func (p *Program) JmpIf(cond bpf.JumpTest, val uint32, trueLabel Label, falseLabel Label) {
p.jumps = append(p.jumps, JumpIf{index: p.currentIndex(), trueLabel: trueLabel, falseLabel: falseLabel})
inst := bpf.JumpIf{Cond: cond, Val: val}
p.instructions = append(p.instructions, inst)
}
// SetLabel sets the label to the latest instruction.
func (p *Program) SetLabel(label Label) {
index := p.currentIndex()
p.labels[label] = append(p.labels[label], index)
}
// Ret inserts a return instruction.
func (p *Program) Ret(action Action) {
if action == ActionErrno {
action |= Action(errnoEPERM)
}
p.instructions = append(p.instructions, bpf.RetConstant{Val: uint32(action)})
}
// LdHi inserts an instruction to load the most significant 32-bit of the 64-bit argument.
func (p *Program) LdHi(arg uint32) {
offset := argumentOffset + sizeOfUint64*arg
if nativeEndian == binary.LittleEndian {
offset += uint32(sizeOfUint32)
}
p.instructions = append(p.instructions, bpf.LoadAbsolute{Off: offset, Size: sizeOfUint32})
}
// LdLo inserts an instruction to load the least significant 32-bit of the 64-bit argument.
func (p *Program) LdLo(arg uint32) {
offset := argumentOffset + sizeOfUint64*arg
if nativeEndian == binary.BigEndian {
offset += uint32(sizeOfUint32)
}
p.instructions = append(p.instructions, bpf.LoadAbsolute{Off: offset, Size: sizeOfUint32})
}
// NewLabel creates a new label. It must be used with SetLabel.
func (p *Program) NewLabel() Label {
p.nextLabel++
return p.nextLabel
}
// Assemble resolves all jump destinations to concrete instructions using the labels.
// This method takes care of long jumps and resolves them by using early returns or unconditional long jumps.
func (p *Program) Assemble() ([]bpf.Instruction, error) {
for _, jump := range p.jumps {
// This is safe since we are only accessing instructions that were inserted as bpf.JumpIf.
jumpInst := p.instructions[jump.index].(bpf.JumpIf)
skip, err := p.resolveLabel(jump, jump.trueLabel)
if err != nil {
return nil, err
}
jumpInst.SkipTrue = skip
skip, err = p.resolveLabel(jump, jump.falseLabel)
if err != nil {
return nil, err
}
jumpInst.SkipFalse = skip
if jumpInst.SkipTrue == 0 && jumpInst.SkipFalse == 0 {
return nil, fmt.Errorf("useless jump found")
}
p.instructions[jump.index] = jumpInst
}
return p.instructions, nil
}
// resolveLabel resolves the label to a short jump.
func (p *Program) resolveLabel(jump JumpIf, label Label) (uint8, error) {
dest := p.labels[label]
skipN := p.computeSkipN(jump, label)
for skipN < 0 {
dest = dest[1:]
if len(dest) == 0 {
return 0, fmt.Errorf("backward jumps are not supported")
}
p.labels[label] = dest
skipN = p.computeSkipN(jump, label)
}
// BPF does not support long conditional jumps.
if skipN > math.MaxUint8 {
insertAfter := findInsertAfter(p.jumps, jump)
// If the jump destination is a return instruction, copy it and add an early return,
// if not, insert a long jump.
jumpDest := p.instructions[dest[0]]
if _, ok := jumpDest.(bpf.RetConstant); !ok {
jumpDest = bpf.Jump{Skip: uint32(skipN - int(insertAfter.index))}
}
insertIndex := p.insertAfter(insertAfter.index, jumpDest)
p.labels[label] = append([]Index{insertIndex}, dest...)
skipN = p.computeSkipN(jump, label)
}
return uint8(skipN), nil
}
// Inserts the instruction after the instruction indicated by index, which must come from p.jumps.
func (p *Program) insertAfter(index Index, inst bpf.Instruction) Index {
// This is safe since we are only accessing instructions that were inserted as bpf.JumpIf.
jumpInst := p.instructions[index].(bpf.JumpIf)
p.instructions[index] = jumpInst
index++
p.instructions = append(p.instructions[:index+1], p.instructions[index:]...)
p.instructions[index] = inst
p.updateIndices(index)
return index
}
// After inserting a new instruction into the instruction list, the indices are wrong.
// This method updates all indices after the instruction point.
func (p *Program) updateIndices(after Index) {
for i := range p.jumps {
if p.jumps[i].index >= after {
p.jumps[i].index++
}
}
for _, v := range p.labels {
for i := range v {
if v[i] >= after {
v[i]++
}
}
}
}
// Computes the number of instructions to skip by resolving the label.
// It might be that the jump is a long jump.
func (p *Program) computeSkipN(jump JumpIf, label Label) int {
dest := p.labels[label]
return int(dest[0]-jump.index) - 1
}
// To insert a new instruction into the instruction list, the furthest jump instruction within
// a short jump is searched.
// It is necessary to search a jump instruction to jump over the new inserted instruction
// and do not disturb the program flow.
func findInsertAfter(jumps []JumpIf, currentJump JumpIf) JumpIf {
insertAfter := currentJump
maxIndex := currentJump.index + 255
for _, jump := range jumps {
if jump.index < maxIndex {
insertAfter = jump
}
}
return insertAfter
}
// Calculate the index of the current instruction.
func (p *Program) currentIndex() Index {
return Index(len(p.instructions))
}