Class: SeccompTools::Syscall

Inherits:
Object
  • Object
show all
Defined in:
lib/seccomp-tools/syscall.rb

Overview

Record syscall number, arguments, return value.

Constant Summary collapse

ABI =

Syscall arguments offset of struct user in different arch.

{
  amd64: { number: 120, args: [112, 104, 96, 56, 72, 44], ret: 80, SYS_prctl: 157, SYS_seccomp: 317 },
  i386: { number: 44, args: [0, 4, 8, 12, 16, 20], ret: 24, SYS_prctl: 172, SYS_seccomp: 354 },
  aarch64: { number: 64, args: [0, 8, 16, 24, 32, 40, 48], ret: 0, SYS_prctl: 167, SYS_seccomp: 277 },
  riscv64: { number: 136, args: [80, 88, 96, 104, 112, 120], ret: 80, SYS_prctl: 167, SYS_seccomp: 277 },
  # Most software invokes syscalls through "svc 0", in which case the syscall number is in r1.
  # However, it's also possible to use "svc NR": this case is not handled here.
  s390x: { number: 24, args: [32, 40, 48, 56, 64, 72], ret: 32, SYS_prctl: 172, SYS_seccomp: 348 }
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pid) ⇒ Syscall

Instantiate a SeccompTools::Syscall object.

Reads the syscall number, arguments and return value of pid through ptrace, so the process must already be stopped and attached.

Parameters:

  • pid (Integer)

    Id of the traced process.

Raises:

  • (ArgumentError)

    If the architecture of pid is not one of ABI's keys.



44
45
46
47
48
49
50
51
52
# File 'lib/seccomp-tools/syscall.rb', line 44

def initialize(pid)
  @pid = pid
  raise ArgumentError, "Only supports #{ABI.keys.join(', ')}" if ABI[arch].nil?

  @abi = ABI[arch]
  @number = peek(abi[:number])
  @args = abi[:args].map { |off| peek(off) }
  @ret = peek(abi[:ret])
end

Instance Attribute Details

#abi{Symbol => Integer, Array<Integer>} (readonly)

Returns The ABI entry of this syscall's architecture.

Returns:

  • ({Symbol => Integer, Array<Integer>})

    The ABI entry of this syscall's architecture.



28
29
30
# File 'lib/seccomp-tools/syscall.rb', line 28

def abi
  @abi
end

#argsArray<Integer> (readonly)

Returns Syscall arguments, in register order.

Returns:

  • (Array<Integer>)

    Syscall arguments, in register order.



32
33
34
# File 'lib/seccomp-tools/syscall.rb', line 32

def args
  @args
end

#numberInteger (readonly)

Returns Syscall number.

Returns:

  • (Integer)

    Syscall number.



30
31
32
# File 'lib/seccomp-tools/syscall.rb', line 30

def number
  @number
end

#pidInteger (readonly)

Returns Id of the traced process.

Returns:

  • (Integer)

    Id of the traced process.



25
26
27
# File 'lib/seccomp-tools/syscall.rb', line 25

def pid
  @pid
end

#retInteger (readonly)

Returns Syscall return value.

Returns:

  • (Integer)

    Syscall return value.



34
35
36
# File 'lib/seccomp-tools/syscall.rb', line 34

def ret
  @ret
end

Class Method Details

.strict_bpf(arch) ⇒ String

Constructs a BPF program equivalent to what SECCOMP_MODE_STRICT enforces: only read, write, exit, and sigreturn are allowed, while any other syscall kills the thread.

Parameters:

  • arch (Symbol)

    Target architecture, one of ABI's keys.

Returns:

  • (String)

    Raw BPF bytes.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/seccomp-tools/syscall.rb', line 89

def self.strict_bpf(arch)
  # Required lazily because the assembler's scanner refers to {ABI} at load time.
  require 'seccomp-tools/asm/asm'

  # The kernel checks sigreturn on architectures that have it and rt_sigreturn on the others.
  sigreturn = Const::Syscall.const_get(arch.to_s.upcase)[:sigreturn] ? :sigreturn : :rt_sigreturn
  Asm.asm(<<-EOS, arch: arch)
    A = sys_number
    A == read ? ok : next
    A == write ? ok : next
    A == exit ? ok : next
    A == #{sigreturn} ? ok : next
    return KILL
    ok:
    return ALLOW
  EOS
end

Instance Method Details

#archSymbol?

Architecture of this syscall, i.e. of the traced process.

Returns:



126
127
128
# File 'lib/seccomp-tools/syscall.rb', line 126

def arch
  @arch ||= Util.process_arch(pid)
end

#dump_bpfString

Dumps the BPF of the filter being installed.

Only meaningful when #set_seccomp? is true. In filter mode args[2] points to a struct sock_fprog in the traced process, whose BPF bytes are read out. Strict mode installs no BPF, so an equivalent filter built by strict_bpf is returned instead.

Returns:

  • (String)

    The raw BPF bytes of the filter being installed.



114
115
116
117
118
119
120
121
# File 'lib/seccomp-tools/syscall.rb', line 114

def dump_bpf
  return self.class.strict_bpf(arch) if strict_mode?

  addr = args[2]
  len = Ptrace.peekdata(pid, addr, 0) & 0xffff # len is unsigned short
  filter = Ptrace.peekdata(pid, addr + (bits / 8), 0) & ((1 << bits) - 1)
  Array.new(len) { |i| Ptrace.peekdata(pid, filter + (i * 8), 0) }.pack('Q*')
end

#filter_mode?Boolean

Is this a +seccomp(SECCOMP_SET_MODE_FILTER, ..)+/+prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ..)+ syscall?

Returns:

  • (Boolean)


67
68
69
70
71
# File 'lib/seccomp-tools/syscall.rb', line 67

def filter_mode?
  return true if number == abi[:SYS_seccomp] && args[0] == Const::BPF::SECCOMP_SET_MODE_FILTER

  number == abi[:SYS_prctl] && args[0] == Const::BPF::PR_SET_SECCOMP && args[1] == Const::BPF::SECCOMP_MODE_FILTER
end

#set_seccomp?Boolean

Is this a seccomp installation syscall?

Both SECCOMP_MODE_FILTER and SECCOMP_MODE_STRICT installations are recognized, invoked through either seccomp or prctl(PR_SET_SECCOMP, ...).

Returns:

  • (Boolean)

    true if this is a seccomp installation syscall.



60
61
62
# File 'lib/seccomp-tools/syscall.rb', line 60

def set_seccomp?
  filter_mode? || strict_mode?
end

#strict_mode?Boolean

Is this a +seccomp(SECCOMP_SET_MODE_STRICT, ..)+/+prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT)+ syscall?

Returns:

  • (Boolean)


76
77
78
79
80
# File 'lib/seccomp-tools/syscall.rb', line 76

def strict_mode?
  return true if number == abi[:SYS_seccomp] && args[0] == Const::BPF::SECCOMP_SET_MODE_STRICT

  number == abi[:SYS_prctl] && args[0] == Const::BPF::PR_SET_SECCOMP && args[1] == Const::BPF::SECCOMP_MODE_STRICT
end