github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/vminfo/linux.go (about) 1 // Copyright 2024 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package vminfo 5 6 import ( 7 "bufio" 8 "bytes" 9 "fmt" 10 "io" 11 "path" 12 "regexp" 13 "sort" 14 "strconv" 15 "strings" 16 17 "github.com/google/syzkaller/sys/targets" 18 ) 19 20 type linux struct { 21 vmType string 22 } 23 24 func (linux) RequiredFiles() []string { 25 return []string{ 26 "/proc/cpuinfo", 27 "/proc/modules", 28 "/proc/kallsyms", 29 "/sys/module/*/sections/.text", 30 "/sys/module/kvm*/parameters/*", 31 } 32 } 33 34 func (linux) CheckFiles() []string { 35 return []string{ 36 "/proc/version", 37 "/proc/filesystems", 38 "/sys/kernel/security/lsm", 39 } 40 } 41 42 func (linux) machineInfos() []machineInfoFunc { 43 return []machineInfoFunc{ 44 linuxReadCPUInfo, 45 linuxReadKVMInfo, 46 } 47 } 48 49 func (linux linux) parseModules(files filesystem) ([]*KernelModule, error) { 50 if linux.vmType == targets.GVisor || linux.vmType == targets.Starnix { 51 return nil, nil 52 } 53 var modules []*KernelModule 54 re := regexp.MustCompile(`(\w+) ([0-9]+) .*(0[x|X][a-fA-F0-9]+)[^\n]*`) 55 modulesText, _ := files.ReadFile("/proc/modules") 56 for _, match := range re.FindAllSubmatch(modulesText, -1) { 57 name := string(match[1]) 58 modAddr, err := strconv.ParseUint(string(match[3]), 0, 64) 59 if err != nil { 60 // /proc/modules is broken, bail out. 61 return nil, fmt.Errorf("module %v address parsing error: %w", name, err) 62 } 63 textAddr, err := linuxModuleTextAddr(files, name) 64 if err != nil { 65 // Module address unavailable, .text is probably 0. Skip this module. 66 continue 67 } 68 modSize, err := strconv.ParseUint(string(match[2]), 0, 64) 69 if err != nil { 70 // /proc/modules is broken, bail out. 71 return nil, fmt.Errorf("module %v size parsing error: %w", name, err) 72 } 73 offset := modAddr - textAddr 74 modules = append(modules, &KernelModule{ 75 Name: name, 76 Addr: textAddr, 77 // The size is wrong as there is overlap in /proc/modules 78 // ex. module1['Addr'] + module1['Size'] > module2['Addr'] 79 // runtime kernel doesn't export .text section size to /sys/module/*/sections/.text 80 // so we need to read it from elf 81 Size: modSize - offset, 82 }) 83 } 84 _stext, _etext, err := linuxParseCoreKernel(files) 85 if err != nil { 86 return nil, err 87 } 88 modules = append(modules, &KernelModule{ 89 Name: "", 90 Addr: _stext, 91 Size: _etext - _stext, 92 }) 93 sort.Slice(modules, func(i, j int) bool { 94 return modules[i].Addr < modules[j].Addr 95 }) 96 return modules, nil 97 } 98 99 func linuxModuleTextAddr(files filesystem, module string) (uint64, error) { 100 data, err := files.ReadFile("/sys/module/" + module + "/sections/.text") 101 if err != nil { 102 return 0, fmt.Errorf("could not read module %v .text address file: %w", module, err) 103 } 104 addrString := strings.TrimSpace(string(data)) 105 addr, err := strconv.ParseUint(addrString, 0, 64) 106 if err != nil { 107 return 0, fmt.Errorf("address parsing error in %v: %w", module, err) 108 } 109 return addr, nil 110 } 111 112 func linuxParseCoreKernel(files filesystem) (uint64, uint64, error) { 113 _Text, _ := files.ReadFile("/proc/kallsyms") 114 re := regexp.MustCompile(`([a-fA-F0-9]+) T _stext\n`) 115 m := re.FindSubmatch(_Text) 116 if m == nil { 117 return 0, 0, fmt.Errorf("failed to get _stext symbol") 118 } 119 _stext, err := strconv.ParseUint("0x"+string(m[1]), 0, 64) 120 if err != nil { 121 return 0, 0, fmt.Errorf("address parsing error in /proc/kallsyms for _stext: %w", err) 122 } 123 // _etext symbol points to the _next_ section, so it has type of the next section. 124 // It can be at least T, D, or R in some cases: 125 // https://groups.google.com/g/syzkaller/c/LSx6YIK_Eeo 126 re = regexp.MustCompile(`([a-fA-F0-9]+) . _etext\n`) 127 m = re.FindSubmatch(_Text) 128 if m == nil { 129 return 0, 0, fmt.Errorf("failed to get _etext symbol") 130 } 131 _etext, err := strconv.ParseUint("0x"+string(m[1]), 0, 64) 132 if err != nil { 133 return 0, 0, fmt.Errorf("address parsing error in /proc/kallsyms for _etext: %w", err) 134 } 135 return _stext, _etext, nil 136 } 137 138 func linuxReadCPUInfo(files filesystem, w io.Writer) (string, error) { 139 data, err := files.ReadFile("/proc/cpuinfo") 140 if err != nil { 141 return "", fmt.Errorf("error reading CPU info:: %w", err) 142 } 143 144 keyIndices := make(map[string]int) 145 type keyValues struct { 146 key string 147 values []string 148 } 149 var info []keyValues 150 for s := bufio.NewScanner(bytes.NewReader(data)); s.Scan(); { 151 splitted := strings.Split(s.Text(), ":") 152 if len(splitted) != 2 { 153 continue 154 } 155 key := strings.TrimSpace(splitted[0]) 156 val := strings.TrimSpace(splitted[1]) 157 if idx, ok := keyIndices[key]; !ok { 158 idx = len(keyIndices) 159 keyIndices[key] = idx 160 info = append(info, keyValues{key, []string{val}}) 161 } else { 162 info[idx].values = append(info[idx].values, val) 163 } 164 } 165 166 for _, kv := range info { 167 // It is guaranteed that len(vals) >= 1 168 key := kv.key 169 vals := kv.values 170 if allEqual(vals) { 171 fmt.Fprintf(w, "%-20s: %s\n", key, vals[0]) 172 } else { 173 fmt.Fprintf(w, "%-20s: %s\n", key, strings.Join(vals, ", ")) 174 } 175 } 176 return "CPU Info", nil 177 } 178 179 func allEqual(slice []string) bool { 180 for i := 1; i < len(slice); i++ { 181 if slice[i] != slice[0] { 182 return false 183 } 184 } 185 return true 186 } 187 188 func linuxReadKVMInfo(files filesystem, w io.Writer) (string, error) { 189 for _, module := range files.ReadDir("/sys/module") { 190 if !strings.HasPrefix(module, "kvm") { 191 continue 192 } 193 paramPath := path.Join("/sys", "module", module, "parameters") 194 fmt.Fprintf(w, "/sys/module/%s:\n", module) 195 for _, param := range files.ReadDir(paramPath) { 196 data, err := files.ReadFile(path.Join(paramPath, param)) 197 if err != nil { 198 return "", fmt.Errorf("error reading KVM info: %w", err) 199 } 200 fmt.Fprintf(w, "\t%s: %s", param, data) 201 } 202 w.Write([]byte{'\n'}) 203 } 204 return "KVM", nil 205 }