github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/binary/elf_package_cataloger.go (about) 1 package binary 2 3 import ( 4 "bytes" 5 "context" 6 "debug/elf" 7 "encoding/binary" 8 "encoding/json" 9 "fmt" 10 11 "github.com/anchore/syft/internal" 12 "github.com/anchore/syft/internal/log" 13 "github.com/anchore/syft/internal/mimetype" 14 "github.com/anchore/syft/internal/unknown" 15 "github.com/anchore/syft/syft/artifact" 16 "github.com/anchore/syft/syft/file" 17 "github.com/anchore/syft/syft/internal/unionreader" 18 "github.com/anchore/syft/syft/pkg" 19 ) 20 21 var _ pkg.Cataloger = (*elfPackageCataloger)(nil) 22 23 type elfPackageCataloger struct { 24 } 25 26 // TODO: for now this accounts for a single data shape from the .note.package section of an ELF binary. 27 // In the future, this should be generalized to support multiple data shapes, including non-json data. 28 // For example, fedora includes an ELF section header as a prefix to the JSON payload: https://github.com/anchore/syft/issues/2713 29 30 type elfBinaryPackageNotes struct { 31 Name string `json:"name"` 32 Version string `json:"version"` 33 PURL string `json:"purl"` 34 CPE string `json:"cpe"` 35 License string `json:"license"` 36 pkg.ELFBinaryPackageNoteJSONPayload `json:",inline"` 37 Location file.Location `json:"-"` 38 } 39 40 type elfPackageKey struct { 41 Name string 42 Version string 43 PURL string 44 CPE string 45 } 46 47 func NewELFPackageCataloger() pkg.Cataloger { 48 return &elfPackageCataloger{} 49 } 50 51 func (c *elfPackageCataloger) Name() string { 52 return "elf-binary-package-cataloger" 53 } 54 55 func (c *elfPackageCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { 56 var errs error 57 locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...) 58 if err != nil { 59 return nil, nil, fmt.Errorf("unable to get binary files by mime type: %w", err) 60 } 61 62 // first find all ELF binaries that have notes 63 var notesByLocation = make(map[elfPackageKey][]elfBinaryPackageNotes) 64 for _, location := range locations { 65 notes, key, err := parseElfPackageNotes(resolver, location, c) 66 if err != nil { 67 errs = unknown.Append(errs, location, err) 68 continue 69 } 70 if notes == nil { 71 continue 72 } 73 notesByLocation[key] = append(notesByLocation[key], *notes) 74 } 75 76 // now we have all ELF binaries that have notes, let's create packages for them. 77 // we do this in a second pass since it is possible that we have multiple ELF binaries with the same name and version 78 // which means the set of binaries collectively represent a single logical package. 79 var pkgs []pkg.Package 80 for _, notes := range notesByLocation { 81 noteLocations := file.NewLocationSet() 82 for _, note := range notes { 83 noteLocations.Add(note.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) 84 } 85 86 // create a package for each unique name/version pair (based on the first note found) 87 pkgs = append(pkgs, newELFPackage(ctx, notes[0], noteLocations)) 88 } 89 90 // why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by 91 // each binary. After all files and packages are processed there is a final task that creates package-to-package 92 // and package-to-file relationships based on the dynamic libraries imported by each binary. 93 return pkgs, nil, errs 94 } 95 96 func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elfPackageCataloger) (*elfBinaryPackageNotes, elfPackageKey, error) { 97 reader, err := resolver.FileContentsByLocation(location) 98 if err != nil { 99 return nil, elfPackageKey{}, fmt.Errorf("unable to get binary contents %q: %w", location.Path(), err) 100 } 101 defer internal.CloseAndLogError(reader, location.AccessPath) 102 103 notes, err := c.parseElfNotes(file.LocationReadCloser{ 104 Location: location, 105 ReadCloser: reader, 106 }) 107 108 if err != nil { 109 log.WithFields("file", location.Path(), "error", err).Trace("unable to parse ELF notes") 110 return nil, elfPackageKey{}, err 111 } 112 113 if notes == nil { 114 return nil, elfPackageKey{}, nil 115 } 116 117 notes.Location = location 118 key := elfPackageKey{ 119 Name: notes.Name, 120 Version: notes.Version, 121 PURL: notes.PURL, 122 CPE: notes.CPE, 123 } 124 return notes, key, nil 125 } 126 127 func (c *elfPackageCataloger) parseElfNotes(reader file.LocationReadCloser) (*elfBinaryPackageNotes, error) { 128 metadata, err := getELFNotes(reader) 129 if err != nil { 130 return nil, fmt.Errorf("unable to process ELF binary: %w", err) 131 } 132 133 if metadata == nil || metadata.Name == "" || metadata.Version == "" { 134 return nil, nil 135 } 136 137 return metadata, nil 138 } 139 140 func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) { 141 unionReader, err := unionreader.GetUnionReader(r) 142 if err != nil { 143 return nil, fmt.Errorf("unable to get union reader for binary: %w", err) 144 } 145 146 f, err := elf.NewFile(unionReader) 147 if f == nil || err != nil { 148 log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to parse binary as ELF") 149 return nil, nil 150 } 151 152 noteSection := f.Section(".note.package") 153 if noteSection == nil { 154 return nil, nil 155 } 156 157 notes, err := noteSection.Data() 158 if err != nil { 159 return nil, err 160 } 161 162 if len(notes) == 0 { 163 return nil, nil 164 } 165 166 { 167 var metadata elfBinaryPackageNotes 168 if err := json.Unmarshal(notes, &metadata); err == nil { 169 return &metadata, nil 170 } 171 } 172 173 { 174 var header elf64SectionHeader 175 headerSize := binary.Size(header) / 4 176 if len(notes) > headerSize { 177 var metadata elfBinaryPackageNotes 178 newPayload := bytes.TrimRight(notes[headerSize:], "\x00") 179 if err = json.Unmarshal(newPayload, &metadata); err == nil { 180 return &metadata, nil 181 } 182 log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON") 183 } 184 } 185 186 return nil, err 187 } 188 189 type elf64SectionHeader struct { 190 ShName uint32 191 ShType uint32 192 ShFlags uint64 193 ShAddr uint64 194 ShOffset uint64 195 ShSize uint64 196 ShLink uint32 197 ShInfo uint32 198 ShAddralign uint64 199 ShEntsize uint64 200 }