inspektor-gadget
247 строк · 6.3 Кб
1// Copyright 2019-2023 The Inspektor Gadget authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//go:build !withoutebpf
16
17package tracer
18
19import (
20"fmt"
21"io"
22"os"
23"path/filepath"
24"regexp"
25"strings"
26
27"github.com/inspektor-gadget/inspektor-gadget/pkg/utils/syscalls"
28)
29
30const syscallsPath = `/sys/kernel/debug/tracing/events/syscalls/`
31
32type param struct {
33position int
34name string
35isPointer bool
36}
37
38type syscallDeclaration struct {
39name string
40params []param
41}
42
43func syscallGetName(nr uint16) string {
44name, ok := syscalls.GetSyscallNameByNumber(int(nr))
45// Just do like strace (https://man7.org/linux/man-pages/man1/strace.1.html):
46// Syscalls unknown to strace are printed raw
47if !ok {
48return fmt.Sprintf("syscall_%x", nr)
49}
50
51return name
52}
53
54// TODO Find all syscalls which take a char * as argument and add them there.
55var syscallDefs = map[string][6]uint64{
56"execve": {useNullByteLength, 0, 0, 0, 0, 0},
57"access": {useNullByteLength, 0, 0, 0, 0, 0},
58"open": {useNullByteLength, 0, 0, 0, 0, 0},
59"openat": {0, useNullByteLength, 0, 0, 0, 0},
60"mkdir": {useNullByteLength, 0, 0, 0, 0, 0},
61"chdir": {useNullByteLength, 0, 0, 0, 0, 0},
62"pivot_root": {useNullByteLength, useNullByteLength, 0, 0, 0, 0},
63"mount": {useNullByteLength, useNullByteLength, useNullByteLength, 0, 0, 0},
64"umount2": {useNullByteLength, 0, 0, 0, 0, 0},
65"sethostname": {useNullByteLength, 0, 0, 0, 0, 0},
66"statfs": {useNullByteLength, 0, 0, 0, 0, 0},
67"stat": {useNullByteLength, 0, 0, 0, 0, 0},
68"statx": {0, useNullByteLength, 0, 0, 0, 0},
69"lstat": {useNullByteLength, 0, 0, 0, 0, 0},
70"fgetxattr": {0, useNullByteLength, 0, 0, 0, 0},
71"lgetxattr": {useNullByteLength, useNullByteLength, 0, 0, 0, 0},
72"getxattr": {useNullByteLength, useNullByteLength, 0, 0, 0, 0},
73"newfstatat": {0, useNullByteLength, 0, 0, 0, 0},
74"read": {0, useRetAsParamLength | paramProbeAtExitMask, 0, 0, 0, 0},
75"write": {0, useArgIndexAsParamLength + 2, 0, 0, 0, 0},
76"getcwd": {useNullByteLength | paramProbeAtExitMask, 0, 0, 0, 0, 0},
77"pread64": {0, useRetAsParamLength | paramProbeAtExitMask, 0, 0, 0, 0},
78}
79
80var re = regexp.MustCompile(`\s+field:(?P<type>.*?) (?P<name>[a-z_0-9]+);.*`)
81
82func parseLine(l string, idx int) (*param, error) {
83n1 := re.SubexpNames()
84
85r := re.FindAllStringSubmatch(l, -1)
86if len(r) == 0 {
87return nil, nil
88}
89res := r[0]
90
91mp := map[string]string{}
92for i, n := range res {
93mp[n1[i]] = n
94}
95
96if _, ok := mp["type"]; !ok {
97return nil, nil
98}
99if _, ok := mp["name"]; !ok {
100return nil, nil
101}
102
103// ignore
104if mp["name"] == "__syscall_nr" {
105return nil, nil
106}
107
108var cParam param
109cParam.name = mp["name"]
110
111// The position is calculated based on the event format. The actual parameters
112// start from 8th index, hence we subtract that from idx to get position
113// of the parameter to the syscall
114cParam.position = idx - 8
115
116cParam.isPointer = strings.Contains(mp["type"], "*")
117
118return &cParam, nil
119}
120
121// Map sys_enter_NAME to syscall name as in /usr/include/asm/unistd_64.h
122// TODO Check if this is also true for arm64.
123func relateSyscallName(name string) string {
124switch name {
125case "newfstat":
126return "fstat"
127case "newlstat":
128return "lstat"
129case "newstat":
130return "stat"
131case "newuname":
132return "uname"
133case "sendfile64":
134return "sendfile"
135case "sysctl":
136return "_sysctl"
137case "umount":
138return "umount2"
139default:
140return name
141}
142}
143
144func parseSyscall(name, format string) (*syscallDeclaration, error) {
145syscallParts := strings.Split(format, "\n")
146var skipped bool
147
148var cParams []param
149for idx, line := range syscallParts {
150if !skipped {
151if len(line) != 0 {
152continue
153} else {
154skipped = true
155}
156}
157cp, err := parseLine(line, idx)
158if err != nil {
159return nil, err
160}
161if cp != nil {
162cParams = append(cParams, *cp)
163}
164}
165
166return &syscallDeclaration{
167name: name,
168params: cParams,
169}, nil
170}
171
172func gatherSyscallsDeclarations() (map[string]syscallDeclaration, error) {
173cSyscalls := make(map[string]syscallDeclaration)
174err := filepath.Walk(syscallsPath, func(path string, f os.FileInfo, err error) error {
175if err != nil {
176return err
177}
178
179if path == "syscalls" {
180return nil
181}
182
183if !f.IsDir() {
184return nil
185}
186
187eventName := f.Name()
188if strings.HasPrefix(eventName, "sys_exit") {
189return nil
190}
191
192syscallName := strings.TrimPrefix(eventName, "sys_enter_")
193syscallName = relateSyscallName(syscallName)
194
195formatFilePath := filepath.Join(syscallsPath, eventName, "format")
196formatFile, err := os.Open(formatFilePath)
197if err != nil {
198return nil
199}
200defer formatFile.Close()
201
202formatBytes, err := io.ReadAll(formatFile)
203if err != nil {
204return err
205}
206
207cSyscall, err := parseSyscall(syscallName, string(formatBytes))
208if err != nil {
209return err
210}
211
212cSyscalls[cSyscall.name] = *cSyscall
213
214return nil
215})
216if err != nil {
217return nil, fmt.Errorf("walking %q: %w", syscallsPath, err)
218}
219return cSyscalls, nil
220}
221
222func getSyscallDeclaration(syscallsDeclarations map[string]syscallDeclaration, syscallName string) (syscallDeclaration, error) {
223declaration, ok := syscallsDeclarations[syscallName]
224if !ok {
225return syscallDeclaration{}, fmt.Errorf("no syscall correspond to %q", syscallName)
226}
227
228return declaration, nil
229}
230
231func (s syscallDeclaration) getParameterCount() uint8 {
232return uint8(len(s.params))
233}
234
235func (s syscallDeclaration) paramIsPointer(paramNumber uint8) (bool, error) {
236if int(paramNumber) >= len(s.params) {
237return false, fmt.Errorf("param number %d out of bounds for syscall %q", paramNumber, s.name)
238}
239return s.params[paramNumber].isPointer, nil
240}
241
242func (s syscallDeclaration) getParameterName(paramNumber uint8) (string, error) {
243if int(paramNumber) >= len(s.params) {
244return "", fmt.Errorf("param number %d out of bounds for syscall %q", paramNumber, s.name)
245}
246return s.params[paramNumber].name, nil
247}
248