auditbeat/tracing/decoder.go (290 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. //go:build linux package tracing import ( "errors" "fmt" "reflect" "sort" "strconv" "strings" "unsafe" ) // This sets a limit on struct decoder's greedy fields. 2048 is not a problem // as the kernel tracing subsystem won't let us dump more than that anyway. const maxRawCopySize = 2048 // Decoder decodes raw events into an usable type. type Decoder interface { // Decode takes a raw message and its metadata and returns a representation // in a decoder-dependent type. Decode(raw []byte, meta Metadata) (interface{}, error) } type mapDecoder []Field // NewMapDecoder creates a new decoder that will parse raw tracing events // into a map[string]interface{}. This decoder will decode all the fields // described in the format. // The map keys are the field names as given in the format. // The map values are fixed-size integers for integer fields: // uint8, uint16, uint32, uint64, and for signed fields, their signed counterpart. // For string fields, the value is a string. // Null string fields will be the null interface. func NewMapDecoder(format ProbeFormat) Decoder { fields := make([]Field, 0, len(format.Fields)) for _, field := range format.Fields { fields = append(fields, field) } sort.Slice(fields, func(i, j int) bool { return fields[i].Offset < fields[j].Offset }) return mapDecoder(fields) } // Decode implements the Decoder interface. func (f mapDecoder) Decode(raw []byte, meta Metadata) (mapIf interface{}, err error) { n := len(raw) m := make(map[string]interface{}, len(f)+1) m["meta"] = meta for _, field := range f { if field.Offset+field.Size > n { return nil, fmt.Errorf("perf event field %s overflows message of size %d", field.Name, n) } var value interface{} ptr := unsafe.Pointer(&raw[field.Offset]) switch field.Type { case FieldTypeInteger: if value, err = readInt(ptr, uint8(field.Size), field.Signed); err != nil { return nil, fmt.Errorf("bad size=%d for integer field=%s", field.Size, field.Name) } case FieldTypeString: offset := int(MachineEndian.Uint16(raw[field.Offset:])) len := int(MachineEndian.Uint16(raw[field.Offset+2:])) if offset+len > n { return nil, fmt.Errorf("perf event string data for field %s overflows message of size %d", field.Name, n) } // (null) strings have data offset equal to string description offset if len != 0 || offset != field.Offset { if len > 0 && raw[offset+len-1] == 0 { len-- } value = string(raw[offset : offset+len]) } } m[field.Name] = value } return m, nil } // AllocateFn is the type of a function that allocates a custom struct // to be used with StructDecoder. This function must return a pointer to // a struct. type AllocateFn func() interface{} type fieldDecoder struct { typ FieldType src uintptr dst uintptr len uintptr name string } type structDecoder struct { alloc AllocateFn fields []fieldDecoder } var intFields = map[reflect.Kind]struct{}{ reflect.Int: {}, reflect.Int8: {}, reflect.Int16: {}, reflect.Int32: {}, reflect.Int64: {}, reflect.Uint: {}, reflect.Uint8: {}, reflect.Uint16: {}, reflect.Uint32: {}, reflect.Uint64: {}, reflect.Uintptr: {}, } const maxIntSizeBytes = 8 // NewStructDecoder creates a new decoder that will parse raw tracing events // into a struct. // // This custom struct has to be annotated so that the required KProbeFormat // fields are stored in the appropriate struct fields, as in: // // type myStruct struct { // Meta tracing.Metadata `kprobe:"metadata"` // Type uint16 `kprobe:"common_type"` // Flags uint8 `kprobe:"common_flags"` // PCount uint8 `kprobe:"common_preempt_count"` // PID uint32 `kprobe:"common_pid"` // IP uint64 `kprobe:"__probe_ip"` // Exe string `kprobe:"exe"` // Fd uint64 `kprobe:"fd"` // Arg3 uint64 `kprobe:"arg3"` // Arg4 uint32 `kprobe:"arg4"` // Arg5 uint16 `kprobe:"arg5"` // Arg6 uint8 `kprobe:"arg6"` // Dump [12]byte `kprobe:"dump,greedy" // } // // There's no need to map all fields captured by fetchargs. // // The special metadata field allows to store the event metadata in the returned // struct. This is optional. The receiver field has to be of Metadata type. // // The special greedy modifier allows a field to capture the named argument // ("dump" in this case) and all the following unnamed arguments after it. // This allows to dump chunks of memory by concatenating successive fetchargs // arguments. // // The custom allocator has to return a pointer to the struct. There's no actual // need to allocate a new struct each time, as long as the consumer of a perf // event channel manages the lifetime of the returned structs. This allows for // pooling of events to reduce allocations. // // This decoder is faster than the map decoder and results in fewer allocations: // Only string fields need to be allocated, plus the allocation by allocFn. func NewStructDecoder(desc ProbeFormat, allocFn AllocateFn) (Decoder, error) { dec := new(structDecoder) dec.alloc = allocFn // Validate that allocFn() returns pointers to structs. sample := allocFn() tSample := reflect.TypeOf(sample) if tSample.Kind() != reflect.Ptr { return nil, errors.New("allocator function doesn't return a pointer") } tSample = tSample.Elem() if tSample.Kind() != reflect.Struct { return nil, errors.New("allocator function doesn't return a pointer to a struct") } var inFieldsByOffset map[int]Field for i := 0; i < tSample.NumField(); i++ { outField := tSample.Field(i) values, found := outField.Tag.Lookup("kprobe") if !found { // Untagged field continue } var name string var allowUndefined bool var greedy bool for idx, param := range strings.Split(values, ",") { switch param { case "allowundefined": // it is okay not to find it in the desc.Fields allowUndefined = true case "greedy": greedy = true default: if idx != 0 { return nil, fmt.Errorf("bad parameter '%s' in kprobe tag for field '%s'", param, outField.Name) } name = param } } // Special handling for metadata field if name == "metadata" { if outField.Type != reflect.TypeOf(Metadata{}) { return nil, errors.New("bad type for meta field") } dec.fields = append(dec.fields, fieldDecoder{ name: name, typ: FieldTypeMeta, dst: outField.Offset, // src&len are unused, this avoids checking len against actual payload src: 0, len: 0, }) continue } inField, found := desc.Fields[name] if !found { if allowUndefined { continue } return nil, fmt.Errorf("field '%s' not found in kprobe format description", name) } if greedy { // When greedy is used for the first time, build a map of kprobe's // fields by the offset they appear. if inFieldsByOffset == nil { inFieldsByOffset = make(map[int]Field) for _, v := range desc.Fields { inFieldsByOffset[v.Offset] = v } } greedySize := uintptr(inField.Size) nextOffset := inField.Offset + inField.Size nextFieldID := -1 for { nextField, found := inFieldsByOffset[nextOffset] if !found { break } if strings.Index(nextField.Name, "arg") != 0 { break } fieldID, err := strconv.Atoi(nextField.Name[3:]) if err != nil { break } if nextFieldID != -1 && nextFieldID != fieldID { break } greedySize += uintptr(nextField.Size) nextOffset += nextField.Size nextFieldID = fieldID + 1 } if greedySize > maxRawCopySize { return nil, fmt.Errorf("greedy field '%s' exceeds limit of %d bytes", outField.Name, maxRawCopySize) } if curSize := outField.Type.Size(); curSize != greedySize { return nil, fmt.Errorf("greedy field '%s' size is %d but greedy requires %d", outField.Name, curSize, greedySize) } dec.fields = append(dec.fields, fieldDecoder{ name: name, typ: FieldTypeRaw, src: uintptr(inField.Offset), dst: outField.Offset, len: greedySize, }) continue } switch inField.Type { case FieldTypeInteger: if _, found := intFields[outField.Type.Kind()]; !found { return nil, fmt.Errorf("wrong struct field type for field '%s', fixed size integer required", name) } if outField.Type.Size() != uintptr(inField.Size) { return nil, fmt.Errorf("wrong struct field size for field '%s', got=%d required=%d", name, outField.Type.Size(), inField.Size) } // Paranoid if inField.Size > maxIntSizeBytes { return nil, fmt.Errorf("fix me: unexpected integer of size %d in field `%s`", inField.Size, name) } case FieldTypeString: if outField.Type.Kind() != reflect.String { return nil, fmt.Errorf("wrong struct field type for field '%s', it should be string", name) } default: // Should not happen return nil, fmt.Errorf("unexpected field type for field '%s'", name) } dec.fields = append(dec.fields, fieldDecoder{ typ: inField.Type, src: uintptr(inField.Offset), dst: outField.Offset, len: uintptr(inField.Size), name: name, }) } sort.Slice(dec.fields, func(i, j int) bool { return dec.fields[i].src < dec.fields[j].src }) return dec, nil } // Decode implements the decoder interface. func (d *structDecoder) Decode(raw []byte, meta Metadata) (s interface{}, err error) { n := uintptr(len(raw)) // Allocate a new struct to fill s = d.alloc() // Get a raw pointer to the struct destPtr := unsafe.Pointer(reflect.ValueOf(s).Pointer()) for _, dec := range d.fields { if dec.src+dec.len > n { return nil, fmt.Errorf("perf event field %s overflows message of size %d", dec.name, n) } switch dec.typ { case FieldTypeInteger: err := copyInt(unsafe.Add(destPtr, dec.dst), unsafe.Pointer(&raw[dec.src]), uint8(dec.len)) if err != nil { return nil, fmt.Errorf("bad size=%d for integer field=%s", dec.len, dec.name) } case FieldTypeString: offset := uintptr(MachineEndian.Uint16(raw[dec.src:])) length := uintptr(MachineEndian.Uint16(raw[dec.src+2:])) if offset+length > n { return nil, fmt.Errorf("perf event string data for field %s overflows message of size %d", dec.name, n) } if length > 0 && raw[offset+length-1] == 0 { length-- } *(*string)(unsafe.Add(destPtr, dec.dst)) = string(raw[offset : offset+length]) case FieldTypeMeta: *(*Metadata)(unsafe.Add(destPtr, dec.dst)) = meta case FieldTypeRaw: copy(unsafe.Slice((*byte)(unsafe.Add(destPtr, dec.dst)), dec.len), raw[dec.src:]) } } return s, nil } type dumpDecoder struct { start int end int } // NewDumpDecoder returns a new decoder that will dump all the arguments // as a byte slice. Useful for memory dumps. Arguments must be: // - unnamed, so they get an automatic argNN name. // - integer of 64bit (u64 / s64). // - dump consecutive memory. func NewDumpDecoder(format ProbeFormat) (Decoder, error) { fields := make([]Field, 0, len(format.Fields)) for name, field := range format.Fields { if strings.Index(name, "arg") != 0 { continue } if field.Type != FieldTypeInteger { return nil, fmt.Errorf("field '%s' is not an integer", name) } if field.Size != 8 { return nil, fmt.Errorf("field '%s' length is not 8", name) } fields = append(fields, field) } if len(fields) == 0 { return nil, errors.New("no fields to decode") } sort.Slice(fields, func(i, j int) bool { return fields[i].Offset < fields[j].Offset }) base := fields[0].Offset end := base for _, field := range fields { if field.Offset != end { return nil, fmt.Errorf("gap before field '%s'", field.Name) } end += field.Size } return &dumpDecoder{ start: base, end: end, }, nil } // Decode implements the decoder interface. func (d *dumpDecoder) Decode(raw []byte, _ Metadata) (interface{}, error) { if len(raw) < d.end { return nil, errors.New("record too short for dump") } return append([]byte(nil), raw[d.start:d.end]...), nil }