Path: blob/main/components/ee/agent-smith/pkg/classifier/sinature.go
2501 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34//go:build linux5// +build linux67package classifier89import (10"bytes"11"debug/elf"12"io"13"regexp"1415"golang.org/x/xerrors"16)1718// ObjectKind describes a type of object a signature can apply to19type ObjectKind string2021const (22// ObjectAny applies to any object23ObjectAny ObjectKind = ""24// ObjectELFSymbols applies to ELF binaries25ObjectELFSymbols ObjectKind = "elf"26// ObjectELFRodata applies to the rodata section of ELF binaries27ObjectELFRodata ObjectKind = "elf-rodata"28)2930// Domain describes where to look for the file to can for a signature31type Domain string3233const (34// DomainProcess process35DomainProcess Domain = "process"36// DomainFileSystem filesystem37DomainFileSystem Domain = "filesystem"38)3940// Signature is an identifying piece of information which can match to file41type Signature struct {42// Name is a description of the signature43Name string `json:"name,omitempty"`4445// Domain describe where to look for the file to search for the signature46// "process" is dominant47// if domain is empty, we set "filesystem"48Domain Domain `json:"domain,omitempty"`4950// The kind of file this signature can apply to51Kind ObjectKind `json:"kind,omitempty"`5253// The pattern of the signature54Pattern []byte `json:"pattern"`5556// If true, the pattern is expected to be a valid regular expression57Regexp bool `json:"regexp"`5859// Checks only a specific section of the file. If the file is smaller than the end of the slice,60// the signature does not match.61Slice Slice `json:"slice,omitempty"`6263// Filenames is a list of filenames this signature can match to64Filename []string `json:"filenames,omitempty"`6566// compiledRegexp is an optimization so that we don't have to re-compile the regexp every time we use it67compiledRegexp *regexp.Regexp68}6970// Slice demarks the area in a stream in which a signature ought to be tested in71type Slice struct {72Start int64 `json:"start,omitempty"`73End int64 `json:"end,omitempty"`74}7576// Validate ensures the signature is valid and thus a file can be matched against it77func (s *Signature) Validate() error {78if len(s.Pattern) == 0 {79return xerrors.Errorf("signature has no pattern")80}81if s.Regexp {82c, err := regexp.Compile(string(s.Pattern))83if err != nil {84return xerrors.Errorf("signature has invalid regexp pattern: %w", err)85}86s.compiledRegexp = c87}88if s.Kind == ObjectELFSymbols && (s.Slice.Start != 0 || s.Slice.End != 0) {89return xerrors.Errorf("cannot use slice with ELF object kind")90}9192if s.Slice.Start < 0 || s.Slice.End < 0 {93return xerrors.Errorf("slice start and end must be positive")94}95if s.Slice.Start != 0 && s.Slice.End != 0 && s.Slice.End <= s.Slice.Start {96return xerrors.Errorf("slice start must be smaller than slice end")97}9899if s.Domain == "" {100s.Domain = DomainFileSystem101}102103return nil104}105106// Matches checks if the signature applies to the stream107func (s *Signature) Matches(in *SignatureReadCache) (bool, error) {108if s.Slice.Start > 0 {109_, err := in.Reader.ReadAt([]byte{}, s.Slice.Start)110// slice start exceeds what we can read - this signature cannot match111if err != nil {112return false, nil113}114}115if s.Slice.End > 0 {116_, err := in.Reader.ReadAt([]byte{}, s.Slice.End)117// slice start exceeds what we can read - this signature cannot match118if err != nil {119return false, nil120}121}122123// check the object kind124if s.Kind != ObjectAny {125var head []byte126if len(in.header) > 0 {127head = in.header128} else {129head = make([]byte, 261)130_, err := in.Reader.ReadAt(head, 0)131if err == io.EOF {132// cannot read header which means that only Any rules would apply133return false, nil134}135if err != nil {136return false, xerrors.Errorf("cannot read stream head: %w", err)137}138in.header = head139}140141matches := false142switch s.Kind {143case ObjectELFSymbols, ObjectELFRodata:144matches = isELF(head)145default:146matches = true147}148if !matches {149return false, nil150}151}152153// necessary to do a string match for text files154if s.Domain == DomainFileSystem {155return s.matchTextFile(in)156}157158// match the specific kind159switch s.Kind {160case ObjectELFSymbols:161return s.matchELF(in)162case ObjectELFRodata:163return s.matchELFRodata(in)164default:165return s.matchAny(in)166}167}168169// elfMagicNumber are the first few bytes of an ELF file170var elfMagicNumber = []byte{0x7f, 0x45, 0x4c, 0x46}171172func isELF(head []byte) bool {173if len(head) < len(elfMagicNumber) {174return false175}176177for i := 0; i < len(elfMagicNumber); i++ {178if head[i] != elfMagicNumber[i] {179return false180}181}182183return true184}185186// matchELF matches a signature against an ELF file187func (s *Signature) matchELFRodata(in *SignatureReadCache) (bool, error) {188var rodata []byte189if len(in.rodata) > 0 {190rodata = in.rodata191} else {192executable, err := elf.NewFile(in.Reader)193if err != nil {194return false, xerrors.Errorf("cannot anaylse ELF file: %w", err)195}196197rodata, err = ExtractELFRodata(executable)198if err != nil {199return false, err200}201in.rodata = rodata202}203204matches, err := s.matches(rodata)205if matches || err != nil {206return matches, err207}208209return false, nil210}211212// matchELF matches a signature against an ELF file213func (s *Signature) matchELF(in *SignatureReadCache) (bool, error) {214var symbols []string215if len(in.symbols) > 0 {216symbols = in.symbols217} else {218executable, err := elf.NewFile(in.Reader)219if err != nil {220return false, xerrors.Errorf("cannot anaylse ELF file: %w", err)221}222223symbols, err = ExtractELFSymbols(executable)224if err != nil {225return false, err226}227in.symbols = symbols228}229230for _, sym := range symbols {231matches, err := s.matches([]byte(sym))232if matches || err != nil {233return matches, err234}235}236237return false, nil238}239240// ExtractELFSymbols extracts all ELF symbol names from an ELF binary241func ExtractELFSymbols(executable *elf.File) ([]string, error) {242syms, err := executable.Symbols()243if err != nil && err != elf.ErrNoSymbols {244return nil, xerrors.Errorf("cannot get dynsym section: %w", err)245}246247dynsyms, err := executable.DynamicSymbols()248if err != nil && err != elf.ErrNoSymbols {249return nil, xerrors.Errorf("cannot get dynsym section: %w", err)250}251252symbols := make([]string, len(syms)+len(dynsyms))253i := 0254for _, s := range syms {255symbols[i] = s.Name256i += 1257}258259for _, s := range dynsyms {260symbols[i] = s.Name261i += 1262}263264return symbols, nil265}266267// ExtractELFRodata extracts the .rodata section268func ExtractELFRodata(executable *elf.File) ([]byte, error) {269data := executable.Section(".rodata")270if data == nil {271// not having a .rodata section is no error in the strict sense272return nil, nil273}274bs, err := data.Data()275if err != nil {276return nil, xerrors.Errorf("cannot get .rodata section: %w", err)277}278return bs, nil279}280281// matchAny matches a signature against a binary file282func (s *Signature) matchAny(in *SignatureReadCache) (bool, error) {283buffer := make([]byte, 8096)284pos := s.Slice.Start285for {286n, err := in.Reader.ReadAt(buffer, pos)287sub := buffer[0:n]288pos += int64(n)289290// TODO: deal with buffer edges (i.e. pattern wrapping around the buffer edge)291if bytes.Contains(sub, s.Pattern) {292return true, nil293}294295if err == io.EOF {296break297}298if err != nil {299return false, xerrors.Errorf("cannot read stream: %w", err)300}301if s.Slice.End > 0 && pos >= s.Slice.End {302break303}304}305306return false, nil307}308309// matchAny matches a signature against a text file310func (s *Signature) matchTextFile(in *SignatureReadCache) (bool, error) {311buffer := make([]byte, 8096)312pos := s.Slice.Start313for {314n, err := in.Reader.ReadAt(buffer, pos)315sub := buffer[0:n]316pos += int64(n)317318match, matchErr := s.matches(sub)319if matchErr != nil {320return false, matchErr321}322if match {323return true, nil324}325326if err == io.EOF {327break328}329if err != nil {330return false, xerrors.Errorf("cannot read stream: %w", err)331}332if s.Slice.End > 0 && pos >= s.Slice.End {333break334}335}336337return false, nil338}339340// matchesString checks if the signature matches a string (respects and caches regexp)341func (s *Signature) matches(v []byte) (bool, error) {342if s.Regexp {343if s.compiledRegexp == nil {344var err error345s.compiledRegexp, err = regexp.Compile(string(s.Pattern))346if err != nil {347return false, xerrors.Errorf("invalid regexp pattern: %w", err)348}349}350351return s.compiledRegexp.Match(v), nil352}353354return bytes.Contains(v, s.Pattern), nil355}356357358