github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/traceloop/tracer/syscall_helpers.go (about)

     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  
    17  package tracer
    18  
    19  import (
    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  
    30  const syscallsPath = `/sys/kernel/debug/tracing/events/syscalls/`
    31  
    32  type param struct {
    33  	position  int
    34  	name      string
    35  	isPointer bool
    36  }
    37  
    38  type syscallDeclaration struct {
    39  	name   string
    40  	params []param
    41  }
    42  
    43  func syscallGetName(nr uint16) string {
    44  	name, 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
    47  	if !ok {
    48  		return fmt.Sprintf("syscall_%x", nr)
    49  	}
    50  
    51  	return name
    52  }
    53  
    54  // TODO Find all syscalls which take a char * as argument and add them there.
    55  var 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  
    80  var re = regexp.MustCompile(`\s+field:(?P<type>.*?) (?P<name>[a-z_0-9]+);.*`)
    81  
    82  func parseLine(l string, idx int) (*param, error) {
    83  	n1 := re.SubexpNames()
    84  
    85  	r := re.FindAllStringSubmatch(l, -1)
    86  	if len(r) == 0 {
    87  		return nil, nil
    88  	}
    89  	res := r[0]
    90  
    91  	mp := map[string]string{}
    92  	for i, n := range res {
    93  		mp[n1[i]] = n
    94  	}
    95  
    96  	if _, ok := mp["type"]; !ok {
    97  		return nil, nil
    98  	}
    99  	if _, ok := mp["name"]; !ok {
   100  		return nil, nil
   101  	}
   102  
   103  	// ignore
   104  	if mp["name"] == "__syscall_nr" {
   105  		return nil, nil
   106  	}
   107  
   108  	var cParam param
   109  	cParam.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
   114  	cParam.position = idx - 8
   115  
   116  	cParam.isPointer = strings.Contains(mp["type"], "*")
   117  
   118  	return &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.
   123  func relateSyscallName(name string) string {
   124  	switch name {
   125  	case "newfstat":
   126  		return "fstat"
   127  	case "newlstat":
   128  		return "lstat"
   129  	case "newstat":
   130  		return "stat"
   131  	case "newuname":
   132  		return "uname"
   133  	case "sendfile64":
   134  		return "sendfile"
   135  	case "sysctl":
   136  		return "_sysctl"
   137  	case "umount":
   138  		return "umount2"
   139  	default:
   140  		return name
   141  	}
   142  }
   143  
   144  func parseSyscall(name, format string) (*syscallDeclaration, error) {
   145  	syscallParts := strings.Split(format, "\n")
   146  	var skipped bool
   147  
   148  	var cParams []param
   149  	for idx, line := range syscallParts {
   150  		if !skipped {
   151  			if len(line) != 0 {
   152  				continue
   153  			} else {
   154  				skipped = true
   155  			}
   156  		}
   157  		cp, err := parseLine(line, idx)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		if cp != nil {
   162  			cParams = append(cParams, *cp)
   163  		}
   164  	}
   165  
   166  	return &syscallDeclaration{
   167  		name:   name,
   168  		params: cParams,
   169  	}, nil
   170  }
   171  
   172  func gatherSyscallsDeclarations() (map[string]syscallDeclaration, error) {
   173  	cSyscalls := make(map[string]syscallDeclaration)
   174  	err := filepath.Walk(syscallsPath, func(path string, f os.FileInfo, err error) error {
   175  		if err != nil {
   176  			return err
   177  		}
   178  
   179  		if path == "syscalls" {
   180  			return nil
   181  		}
   182  
   183  		if !f.IsDir() {
   184  			return nil
   185  		}
   186  
   187  		eventName := f.Name()
   188  		if strings.HasPrefix(eventName, "sys_exit") {
   189  			return nil
   190  		}
   191  
   192  		syscallName := strings.TrimPrefix(eventName, "sys_enter_")
   193  		syscallName = relateSyscallName(syscallName)
   194  
   195  		formatFilePath := filepath.Join(syscallsPath, eventName, "format")
   196  		formatFile, err := os.Open(formatFilePath)
   197  		if err != nil {
   198  			return nil
   199  		}
   200  		defer formatFile.Close()
   201  
   202  		formatBytes, err := io.ReadAll(formatFile)
   203  		if err != nil {
   204  			return err
   205  		}
   206  
   207  		cSyscall, err := parseSyscall(syscallName, string(formatBytes))
   208  		if err != nil {
   209  			return err
   210  		}
   211  
   212  		cSyscalls[cSyscall.name] = *cSyscall
   213  
   214  		return nil
   215  	})
   216  	if err != nil {
   217  		return nil, fmt.Errorf("walking %q: %w", syscallsPath, err)
   218  	}
   219  	return cSyscalls, nil
   220  }
   221  
   222  func getSyscallDeclaration(syscallsDeclarations map[string]syscallDeclaration, syscallName string) (syscallDeclaration, error) {
   223  	declaration, ok := syscallsDeclarations[syscallName]
   224  	if !ok {
   225  		return syscallDeclaration{}, fmt.Errorf("no syscall correspond to %q", syscallName)
   226  	}
   227  
   228  	return declaration, nil
   229  }
   230  
   231  func (s syscallDeclaration) getParameterCount() uint8 {
   232  	return uint8(len(s.params))
   233  }
   234  
   235  func (s syscallDeclaration) paramIsPointer(paramNumber uint8) (bool, error) {
   236  	if int(paramNumber) >= len(s.params) {
   237  		return false, fmt.Errorf("param number %d out of bounds for syscall %q", paramNumber, s.name)
   238  	}
   239  	return s.params[paramNumber].isPointer, nil
   240  }
   241  
   242  func (s syscallDeclaration) getParameterName(paramNumber uint8) (string, error) {
   243  	if int(paramNumber) >= len(s.params) {
   244  		return "", fmt.Errorf("param number %d out of bounds for syscall %q", paramNumber, s.name)
   245  	}
   246  	return s.params[paramNumber].name, nil
   247  }