|
4 | 4 | package resources |
5 | 5 |
|
6 | 6 | import ( |
| 7 | + "debug/elf" |
7 | 8 | "errors" |
| 9 | + "io" |
| 10 | + "os" |
| 11 | + "path/filepath" |
8 | 12 | "regexp" |
| 13 | + "strings" |
9 | 14 | "sync" |
10 | 15 |
|
11 | 16 | "github.com/rs/zerolog/log" |
12 | 17 | "go.mondoo.com/cnquery/v12/llx" |
13 | 18 | "go.mondoo.com/cnquery/v12/providers-sdk/v1/plugin" |
14 | 19 | "go.mondoo.com/cnquery/v12/providers/os/connection/shared" |
| 20 | + "go.mondoo.com/cnquery/v12/providers/os/connection/tar" |
15 | 21 | "go.mondoo.com/cnquery/v12/providers/os/resources/packages" |
16 | 22 | "go.mondoo.com/cnquery/v12/types" |
17 | 23 | "go.mondoo.com/cnquery/v12/utils/multierr" |
18 | 24 | ) |
19 | 25 |
|
20 | | -var PKG_IDENTIFIER = regexp.MustCompile(`^(.*):\/\/(.*)\/(.*)\/(.*)$`) |
| 26 | +var ( |
| 27 | + PKG_IDENTIFIER = regexp.MustCompile(`^(.*):\/\/(.*)\/(.*)\/(.*)$`) |
| 28 | + // semverRegEx = regexp.MustCompile(`[^\d\w](v?\d+\.\d+\.\d+)[^\d\w]`) |
| 29 | + semverRegEx = regexp.MustCompile(`\x{0000}((:?[\w]+/)?v?\d+\.\d+\.\d+)\x{0000}`) |
| 30 | +) |
21 | 31 |
|
22 | 32 | // A system package cannot be installed twice but there are edge cases: |
23 | 33 | // - the same package name could be installed for multiple archs |
@@ -139,11 +149,148 @@ type mqlPackagesInternal struct { |
139 | 149 | packagesByName map[string]*mqlPackage |
140 | 150 | } |
141 | 151 |
|
| 152 | +type elfBinaryPackageNotes struct { |
| 153 | + Name string `json:"name"` |
| 154 | + Version string `json:"version"` |
| 155 | + PURL string `json:"purl"` |
| 156 | + CPE string `json:"cpe"` |
| 157 | + License string `json:"license"` |
| 158 | +} |
| 159 | + |
142 | 160 | func (x *mqlPackages) list() ([]any, error) { |
143 | 161 | x.lock.Lock() |
144 | 162 | defer x.lock.Unlock() |
145 | 163 |
|
146 | 164 | conn := x.MqlRuntime.Connection.(shared.Connection) |
| 165 | + |
| 166 | + binaryPkgs := []packages.Package{} |
| 167 | + tarConn := conn.(*tar.Connection) |
| 168 | + tarFs := tarConn.FileSystem().(*tar.FS) |
| 169 | + perm := uint32(0o777) |
| 170 | + depth := int(1) |
| 171 | + pathsToScan := []string{ |
| 172 | + "/fluent-bit/bin", |
| 173 | + "/bin", |
| 174 | + "/sbin", |
| 175 | + "/usr/bin", |
| 176 | + "/usr/sbin", |
| 177 | + "/usr/local/bin", |
| 178 | + "/usr/local/sbin", |
| 179 | + "/", |
| 180 | + } |
| 181 | + supportedFiles := []string{ |
| 182 | + "fluent-bit", |
| 183 | + "gitlab-runner", |
| 184 | + "nginx", |
| 185 | + } |
| 186 | + for _, path := range pathsToScan { |
| 187 | + files, err := tarFs.Find(path, nil, "file", &perm, &depth) |
| 188 | + if err != nil { |
| 189 | + return nil, err |
| 190 | + } |
| 191 | + |
| 192 | + for _, file := range files { |
| 193 | + log.Debug().Str("file", file).Msg("found file") |
| 194 | + // TODO: we need a reject list here, well known OS binaries are not interesting |
| 195 | + supported := false |
| 196 | + for _, supportedFile := range supportedFiles { |
| 197 | + if strings.HasSuffix(file, supportedFile) { |
| 198 | + supported = true |
| 199 | + } |
| 200 | + } |
| 201 | + if !supported { |
| 202 | + continue |
| 203 | + } |
| 204 | + f, err := tarFs.Open(file) |
| 205 | + if err != nil { |
| 206 | + return nil, err |
| 207 | + } |
| 208 | + defer f.Close() |
| 209 | + |
| 210 | + tmpFile, err := os.CreateTemp("", "mondoo-binary-package-notes-") |
| 211 | + if err != nil { |
| 212 | + log.Error().Err(err).Msg("could not copy file to temp file") |
| 213 | + continue |
| 214 | + } |
| 215 | + defer os.Remove(tmpFile.Name()) |
| 216 | + |
| 217 | + _, err = io.Copy(tmpFile, f) |
| 218 | + if err != nil { |
| 219 | + log.Error().Err(err).Msg("could not create temp file") |
| 220 | + continue |
| 221 | + } |
| 222 | + tmpFile.Close() |
| 223 | + f, err = os.Open(tmpFile.Name()) |
| 224 | + if err != nil { |
| 225 | + log.Error().Err(err).Msg("could not open temp file") |
| 226 | + continue |
| 227 | + } |
| 228 | + defer f.Close() |
| 229 | + |
| 230 | + // chunk := make([]byte, 1024*1024*3) |
| 231 | + // start := int64(0) |
| 232 | + // for { |
| 233 | + // numBytes, err := f.ReadAt(chunk, start) |
| 234 | + // if err != nil { |
| 235 | + // log.Error().Err(err).Msg("could not read first block") |
| 236 | + // break |
| 237 | + // } |
| 238 | + // m := semverRegEx.FindAllStringSubmatch(string(chunk), -1) |
| 239 | + // log.Debug().Interface("versions", m).Msg("found version") |
| 240 | + // if numBytes < 1024*1024*3 { |
| 241 | + // break |
| 242 | + // } |
| 243 | + |
| 244 | + // start += int64(len(chunk) - 100) |
| 245 | + // } |
| 246 | + |
| 247 | + elfBinary, err := elf.NewFile(f) |
| 248 | + if err != nil { |
| 249 | + log.Error().Err(err).Msg("could not read elf file") |
| 250 | + continue |
| 251 | + } |
| 252 | + for _, section := range elfBinary.Sections { |
| 253 | + // if section.Name == ".text" || section.Name == ".dynstr" || section.Name == ".tbss" || section.Name == ".bss" { |
| 254 | + if section.Name != ".rodata" { |
| 255 | + continue |
| 256 | + } |
| 257 | + notes, err := section.Data() |
| 258 | + if err != nil { |
| 259 | + log.Error().Err(err).Msg("could not read section data") |
| 260 | + continue |
| 261 | + } |
| 262 | + log.Debug().Interface("section", section.Name).Msg("found section") |
| 263 | + notesStr := string(notes) |
| 264 | + m := semverRegEx.FindStringSubmatch(notesStr) |
| 265 | + if len(m) > 0 { |
| 266 | + log.Debug().Str("version", m[1]).Msg("found version") |
| 267 | + pkgName := filepath.Base(file) |
| 268 | + binPkg := packages.Package{ |
| 269 | + Name: pkgName, |
| 270 | + Version: m[1], |
| 271 | + Arch: "amd64", |
| 272 | + Format: "binary", |
| 273 | + PUrl: "pkg:binary/" + pkgName, |
| 274 | + } |
| 275 | + binaryPkgs = append(binaryPkgs, binPkg) |
| 276 | + log.Debug().Str("package", binPkg.Name).Str("version", binPkg.Version).Str("arch", binPkg.Arch).Str("format", binPkg.Format).Str("purl", binPkg.PUrl).Msg("found binary package") |
| 277 | + } |
| 278 | + |
| 279 | + } |
| 280 | + |
| 281 | + // var metadata elfBinaryPackageNotes |
| 282 | + // if err := json.Unmarshal(notes, &metadata); err == nil { |
| 283 | + // binaryPkgs = append(binaryPkgs, packages.Package{ |
| 284 | + // Name: metadata.Name, |
| 285 | + // Version: metadata.Version, |
| 286 | + // Arch: "amd64", |
| 287 | + // Format: "binary", |
| 288 | + // PUrl: metadata.PURL, |
| 289 | + // }) |
| 290 | + // } |
| 291 | + } |
| 292 | + } |
| 293 | + |
147 | 294 | pms, err := packages.ResolveSystemPkgManagers(conn) |
148 | 295 | if len(pms) == 0 || err != nil { |
149 | 296 | return nil, errors.New("could not detect suitable package manager for platform") |
@@ -178,6 +325,8 @@ func (x *mqlPackages) list() ([]any, error) { |
178 | 325 | availableMap[a.Name+"/"+a.Arch] = a |
179 | 326 | } |
180 | 327 |
|
| 328 | + osPkgs = append(osPkgs, binaryPkgs...) |
| 329 | + |
181 | 330 | // create MQL package os for each package |
182 | 331 | pkgs := make([]any, len(osPkgs)) |
183 | 332 | for i, osPkg := range osPkgs { |
|
0 commit comments