rule/rule.go (889 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.
package rule
import (
"errors"
"fmt"
"math"
"os"
"os/user"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/elastic/go-libaudit/v2/auparse"
)
//go:generate sh -c "go tool cgo -godefs defs_kernel_types.go > zkernel_types.go && gofmt -w zkernel_types.go"
//go:generate go run github.com/elastic/go-licenser
const (
maxKeyLength = 256 // AUDIT_MAX_KEY_LEN
pathMax = 4096 // PATH_MAX
keySeparator = 0x01 // AUDIT_KEY_SEPARATOR
)
// Build builds an audit rule.
func Build(rule Rule) (WireFormat, error) {
data := &ruleData{allSyscalls: true}
var err error
switch v := rule.(type) {
case *SyscallRule:
if err = data.setList(v.List); err != nil {
return nil, err
}
if err = data.setAction(v.Action); err != nil {
return nil, err
}
for _, filter := range v.Filters {
switch filter.Type {
case ValueFilterType:
if err = addFilter(data, filter.LHS, filter.Comparator, filter.RHS); err != nil {
return nil, fmt.Errorf("failed to add filter '%v': %w", filter, err)
}
case InterFieldFilterType:
if err = addInterFieldComparator(data, filter.LHS, filter.Comparator, filter.RHS); err != nil {
return nil, fmt.Errorf("failed to add interfield comparison '%v': %w", filter, err)
}
}
}
for _, syscall := range v.Syscalls {
if err = addSyscall(data, syscall); err != nil {
return nil, fmt.Errorf("failed to add syscall '%v': %w", syscall, err)
}
}
if err = addKeys(data, v.Keys); err != nil {
return nil, err
}
case *FileWatchRule:
if err = addFileWatch(data, v); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown rule type: %T", v)
}
ard, err := data.toAuditRuleData()
if err != nil {
return nil, err
}
return ard.toWireFormat(), nil
}
// ToCommandLine decodes a WireFormat into a command-line rule.
// When resolveIds is set, it tries to resolve the argument to UIDs, GIDs,
// file_type fields.
// `auditctl -l` always prints the numeric (non-resolved) representation of
// this fields, so when the flag is set to false, the output is the same as
// auditctl.
// There is an exception to this rule when parsing the `arch` field:
// auditctl always prints "b64" or "b32" even for architectures other than
// the current machine. This is misleading, so this code will print the actual
// architecture.
func ToCommandLine(wf WireFormat, resolveIds bool) (rule string, err error) {
ar, err := fromWireFormat(wf)
if err != nil {
return "", fmt.Errorf("failed to parse wire format: %w", err)
}
r := ruleData{}
if err = r.fromAuditRuleData(ar); err != nil {
return "", fmt.Errorf("failed to parse audit rule: %w", err)
}
list, err := r.getList()
if err != nil {
return "", err
}
act, err := r.getAction()
if err != nil {
return "", err
}
existingFields := make(map[field]int)
for idx, fieldID := range r.fields {
existingFields[fieldID] = idx
}
// Detect if rule is a watch.
// Must have all syscalls and perm field. Only other valid fields are
// dir, path and key, according to auditctl source
if permIdx, ok := existingFields[permField]; r.allSyscalls && ok {
extraFields, pos := false, 0
var path, key string
loop:
for _, fieldID := range r.fields {
switch fieldID {
case keyField, pathField, dirField:
if pos >= len(r.strings) {
return "", fmt.Errorf("no buffer data for path field %d", fieldID)
}
if fieldID == keyField {
key = r.strings[pos]
} else {
path = r.strings[pos]
}
pos++
case permField:
default:
extraFields = true
break loop
}
}
if !extraFields {
arguments := []string{"-w", path, "-p", permission(r.values[permIdx]).String()}
if len(key) > 0 {
arguments = append(arguments, "-k", key)
}
return strings.Join(arguments, " "), nil
}
}
// Parse rule as syscall type
arguments := []string{
"-a",
fmt.Sprintf("%s,%s", act, list),
}
// Parse arch field first, if present
// Here there is a significant difference to what auditctl does.
// Auditctl will allow to install a rule for a different platform
// (i.e. "aarch64" when the actual platform is "x86_64"). A rule like this
// will never trigger any events in the kernel.
// When such a rule is printed with `auditctl -l`, it will show as
// "-F arch=b64", which is wrong.
// This code will print the real value, "aarch64".
if fieldIdx, found := existingFields[archField]; found {
r.arch, err = getDisplayArch(r.values[fieldIdx])
if err != nil {
return "", err
}
arguments = append(arguments, "-F", fmt.Sprintf("arch=%s", r.arch))
}
// Parse syscalls
if r.allSyscalls {
if r.flags == exitFilter || r.flags == entryFilter {
arguments = append(arguments, "-S", "all")
}
} else if len(r.syscalls) > 0 {
arch, err := getRuntimeArch()
if err != nil {
return "", err
}
if r.arch == "b32" {
switch arch {
case "i386", "arm", "ppc", "s390":
case "aarch64":
arch = "arm"
case "x86_64":
arch = "i386"
case "ppc64", "ppc64le":
arch = "ppc"
case "s390x":
arch = "s390"
default:
return "", fmt.Errorf("invalid arch for b32: '%s'", arch)
}
} else if len(r.arch) > 0 && r.arch != "b64" {
arch = r.arch
}
syscallTable, ok := auparse.AuditSyscalls[arch]
if !ok {
return "", fmt.Errorf("no syscall table for arch %s", arch)
}
list := make([]string, len(r.syscalls))
for idx, syscallID := range r.syscalls {
list[idx], ok = syscallTable[int(syscallID)]
if !ok {
return "", fmt.Errorf("syscall %d not found for arch %s", syscallID, arch)
}
}
arguments = append(arguments, "-S", strings.Join(list, ","))
}
// Parse fields
stringIndex := 0
for idx, fieldID := range r.fields {
op, found := reverseOperatorsTable[r.fieldFlags[idx]]
if !found {
return "", fmt.Errorf("field operator %x not found", r.fieldFlags[idx])
}
switch fieldID {
case archField:
// arch already handled
case fieldCompare:
fieldIds, found := reverseComparisonsTable[comparison(r.values[idx])]
if !found {
return "", errors.New("comparison code not valid")
}
if fieldIds[1] < fieldIds[0] {
fieldIds[0], fieldIds[1] = fieldIds[1], fieldIds[0]
}
var fields [2]string
for idx, id := range fieldIds {
if fields[idx], found = reverseFieldsTable[id]; !found {
return "", fmt.Errorf("unknown field %d", id)
}
}
arguments = append(arguments, fmt.Sprintf("-C %s%s%s",
fields[0], op, fields[1]))
default:
lhs, found := reverseFieldsTable[fieldID]
if !found {
return "", fmt.Errorf("field %x not found", fieldID)
}
value := r.values[idx]
var rhs string
switch fieldID {
// Fields that take a string
case objectUserField, objectRoleField, objectTypeField, objectLevelLowField,
objectLevelHighField, pathField, dirField, subjectUserField,
subjectRoleField, subjectTypeField, subjectSensitivityField,
subjectClearanceField, keyField, exeField:
if stringIndex >= len(r.strings) {
return "", errors.New("string buffer overflow")
}
rhs = r.strings[stringIndex]
stringIndex++
case exitField:
exitCode := int(int32(value))
if errnoValue, ok := auparse.AuditErrnoToName[-exitCode]; ok {
rhs = fmt.Sprintf("-%s", errnoValue)
} else {
rhs = strconv.Itoa(exitCode)
}
case uidField, euidField, suidField, fsuidField, auidField, objectUIDField:
rhs = strconv.Itoa(int(int32(value)))
if resolveIds {
if user, err := user.LookupId(rhs); err == nil {
rhs = user.Username
}
}
case gidField, egidField, sgidField, fsgidField, objectGIDField:
rhs = strconv.Itoa(int(int32(value)))
if resolveIds {
if group, err := user.LookupGroupId(rhs); err == nil {
rhs = group.Name
}
}
case msgTypeField:
if value <= math.MaxUint16 {
rhs = auparse.AuditMessageType(value).String()
} else {
rhs = fmt.Sprintf("UNKNOWN[%d]", value)
}
case permField:
rhs = permission(value).String()
case filetypeField:
if resolveIds {
rhs = filetype(value).String()
} else {
rhs = strconv.Itoa(int(value))
}
default:
rhs = strconv.Itoa(int(value))
}
arguments = append(arguments, fmt.Sprintf("-F %s%s%s", lhs, op, rhs))
}
}
return strings.Join(arguments, " "), nil
}
func addFileWatch(data *ruleData, rule *FileWatchRule) error {
path := filepath.Clean(rule.Path)
if !filepath.IsAbs(path) {
return fmt.Errorf("path must be absolute: %v", path)
}
watchType := "path"
if info, err := os.Stat(path); err == nil && info.IsDir() {
watchType = "dir"
}
var perms string
if len(rule.Permissions) == 0 {
perms = "rwxa"
} else {
perms = ""
for _, p := range rule.Permissions {
switch p {
case ReadAccessType:
perms += "r"
case WriteAccessType:
perms += "w"
case ExecuteAccessType:
perms += "x"
case AttributeChangeAccessType:
perms += "a"
}
}
}
// Build rule.
data.flags = exitFilter
data.action = alwaysAction
data.allSyscalls = true
if err := addFilter(data, watchType, "=", path); err != nil {
return err
}
if err := addFilter(data, "perm", "=", perms); err != nil {
return err
}
if err := addKeys(data, rule.Keys); err != nil {
return err
}
return nil
}
func addKeys(data *ruleData, keys []string) error {
if len(keys) > 0 {
key := strings.Join(keys, string(rune(keySeparator)))
if err := addFilter(data, "key", "=", key); err != nil {
return fmt.Errorf("failed to add keys [%v]: %w", strings.Join(keys, ","), err)
}
}
return nil
}
type ruleData struct {
flags filter
action action
allSyscalls bool
syscalls []uint32
fields []field
values []uint32
fieldFlags []operator
strings []string
arch string
}
func (r ruleData) toAuditRuleData() (*auditRuleData, error) {
data := &auditRuleData{auditRuleHeader: auditRuleHeader{
Flags: r.flags,
Action: r.action,
FieldCount: uint32(len(r.fields)),
}}
if r.allSyscalls {
for i := range data.Mask {
data.Mask[i] = 0xFFFFFFFF
}
// NOTE: This was added to match the binary output when listing rules
// from the kernel. See https://github.com/elastic/go-libaudit/pull/97.
data.Mask[len(data.Mask)-1] = 0x0000FFFF
} else {
for _, syscallNum := range r.syscalls {
word := syscallNum / 32
bit := 1 << (syscallNum - (word * 32))
if int(word) > len(data.Mask) {
return nil, fmt.Errorf("invalid syscall number %v", syscallNum)
}
data.Mask[word] |= uint32(bit)
}
}
if len(r.fields) > len(data.Fields) {
return nil, fmt.Errorf("too many filters and keys, only %v total are supported", len(data.Fields))
}
for i := range r.fields {
data.Fields[i] = r.fields[i]
data.FieldFlags[i] = r.fieldFlags[i]
data.Values[i] = r.values[i]
}
for _, s := range r.strings {
data.Buf = append(data.Buf, []byte(s)...)
}
data.BufLen = uint32(len(data.Buf))
return data, nil
}
func (r *ruleData) fromAuditRuleData(in *auditRuleData) error {
r.flags = in.Flags
r.action = in.Action
r.fields = make([]field, in.FieldCount)
r.allSyscalls = true
for i := 0; r.allSyscalls && i < len(in.Mask)-1; i++ {
r.allSyscalls = in.Mask[i] == 0xFFFFFFFF
}
if !r.allSyscalls {
for word, bits := range in.Mask {
for bit := uint32(0); bit < 32; bit++ {
if bits&(1<<bit) != 0 {
r.syscalls = append(r.syscalls, uint32(word)*32+bit)
}
}
}
}
r.fields = make([]field, in.FieldCount)
r.fieldFlags = make([]operator, in.FieldCount)
r.values = make([]uint32, in.FieldCount)
offset := uint32(0)
for i := uint32(0); i < in.FieldCount; i++ {
r.fields[i] = in.Fields[i]
r.fieldFlags[i] = in.FieldFlags[i]
r.values[i] = in.Values[i]
switch r.fields[i] {
case objectUserField, objectRoleField, objectTypeField, objectLevelLowField,
objectLevelHighField, pathField, dirField, subjectUserField,
subjectRoleField, subjectTypeField, subjectSensitivityField,
subjectClearanceField, keyField, exeField:
end := in.Values[i] + offset
if end > in.BufLen {
return fmt.Errorf("field %d overflows buffer", i)
}
r.strings = append(r.strings, string(in.Buf[offset:end]))
offset = end
}
}
return nil
}
func (r *ruleData) setList(list string) error {
switch list {
case "exit":
r.flags = exitFilter
case "task":
r.flags = taskFilter
case "user":
r.flags = userFilter
case "exclude":
r.flags = excludeFilter
default:
return fmt.Errorf("invalid list '%v'", list)
}
return nil
}
func (r *ruleData) getList() (string, error) {
switch r.flags {
case exitFilter:
return "exit", nil
case taskFilter:
return "task", nil
case userFilter:
return "user", nil
case excludeFilter:
return "exclude", nil
default:
return "", fmt.Errorf("invalid list flag '%v'", r.flags)
}
}
func (r *ruleData) setAction(action string) error {
switch action {
case "always":
r.action = alwaysAction
case "never":
r.action = neverAction
default:
return fmt.Errorf("invalid action '%v'", action)
}
return nil
}
func (r *ruleData) getAction() (string, error) {
switch r.action {
case alwaysAction:
return "always", nil
case neverAction:
return "never", nil
default:
return "", fmt.Errorf("invalid action '%v'", r.action)
}
}
// Convert name to number.
// Look for conditions when arch needs to be specified.
// Add syscall bit to mask.
func addSyscall(rule *ruleData, syscall string) error {
if syscall == "all" {
rule.allSyscalls = true
return nil
}
rule.allSyscalls = false
syscallNum, err := strconv.Atoi(syscall)
if err != nil {
if !errors.Is(err, strconv.ErrSyntax) {
return fmt.Errorf("failed to parse syscall number '%v': %w", syscall, err)
}
arch := rule.arch
if arch == "" {
arch, err = getRuntimeArch()
if err != nil {
return fmt.Errorf("failed to add syscall: %w", err)
}
}
// Convert name to number.
table, found := reverseSyscall[arch]
if !found {
return fmt.Errorf("syscall table not found for arch %v", arch)
}
syscallNum, found = table[syscall]
if !found {
return fmt.Errorf("unknown syscall '%v' for arch %v", syscall, arch)
}
}
rule.syscalls = append(rule.syscalls, uint32(syscallNum))
return nil
}
func addInterFieldComparator(rule *ruleData, lhs, comparator, rhs string) error {
op, found := operatorsTable[comparator]
if !found {
return fmt.Errorf("invalid operator '%v'", comparator)
}
switch op {
case equalOperator, notEqualOperator:
default:
return fmt.Errorf("invalid operator '%v', only '=' or '!=' can be used", comparator)
}
leftField, found := fieldsTable[lhs]
if !found {
return fmt.Errorf("invalid field '%v' on left", lhs)
}
rightField, found := fieldsTable[rhs]
if !found {
return fmt.Errorf("invalid field '%v' on right", lhs)
}
table, found := comparisonsTable[leftField]
if !found {
return fmt.Errorf("field '%v' cannot be used in an interfield comparison", lhs)
}
comparison, found := table[rightField]
if !found {
return fmt.Errorf("field '%v' cannot be used in an interfield comparison", rhs)
}
rule.fields = append(rule.fields, fieldCompare)
rule.fieldFlags = append(rule.fieldFlags, op)
rule.values = append(rule.values, uint32(comparison))
return nil
}
func addFilter(rule *ruleData, lhs, comparator, rhs string) error {
op, found := operatorsTable[comparator]
if !found {
return fmt.Errorf("invalid operator '%v'", comparator)
}
field, found := fieldsTable[lhs]
if !found {
return fmt.Errorf("invalid field '%v' on left", lhs)
}
// Only newer kernel versions support exclude for credential types. Older
// kernels only support exclude on the msgtype field.
// https://github.com/torvalds/linux/blob/v5.16/kernel/auditfilter.c#L1343
if rule.flags == excludeFilter {
switch field {
case pidField, uidField, gidField, auidField, msgTypeField,
subjectUserField, subjectRoleField, subjectTypeField,
subjectSensitivityField, subjectClearanceField,
exeField:
default:
return fmt.Errorf("field '%v' cannot be used the exclude flag", lhs)
}
}
switch field {
case uidField, euidField, suidField, fsuidField, auidField, objectUIDField:
// Convert RHS to number.
// Or attempt to lookup the name to get the number.
uid, err := getUID(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, uid)
case gidField, egidField, sgidField, fsgidField, objectGIDField:
gid, err := getGID(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, gid)
case exitField:
// Flag must be FilterExit.
if rule.flags != exitFilter {
return errors.New("exit filter can only be applied to syscall exit")
}
exitCode, err := getExitCode(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, uint32(exitCode))
case msgTypeField:
// Flag must be exclude or user.
if rule.flags != userFilter && rule.flags != excludeFilter {
return errors.New("msgtype filter can only be applied to the user or exclude lists")
}
msgType, err := getAuditMsgType(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, msgType)
case objectUserField, objectRoleField, objectTypeField, objectLevelLowField,
objectLevelHighField, pathField, dirField:
// Flag must be FilterExit.
if rule.flags != exitFilter {
return fmt.Errorf("%v filter can only be applied to the syscall exit", lhs)
}
fallthrough
case subjectUserField, subjectRoleField, subjectTypeField,
subjectSensitivityField, subjectClearanceField, keyField, exeField:
// Add string to strings.
if field == keyField && len(rhs) > maxKeyLength {
return fmt.Errorf("%v cannot be longer than %v", lhs, maxKeyLength)
} else if len(rhs) > pathMax {
return fmt.Errorf("%v cannot be longer than %v", lhs, pathMax)
}
rule.values = append(rule.values, uint32(len(rhs)))
rule.strings = append(rule.strings, rhs)
case archField:
// Arch should come before syscall.
// Arch only supports = and !=.
if op != equalOperator && op != notEqualOperator {
return fmt.Errorf("arch only supports the = and != operators")
}
// Or convert name to arch or validate given arch.
archName, arch, err := getArch(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, arch)
rule.arch = archName
case permField:
// Perm is only valid for exit.
if rule.flags != exitFilter {
return fmt.Errorf("perm filter can only be applied to the syscall exit")
}
// Perm is only valid for =.
if op != equalOperator {
return fmt.Errorf("perm only support the = operator")
}
perm, err := getPerm(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, perm)
case filetypeField:
// Filetype is only valid for exit.
if rule.flags != exitFilter {
return fmt.Errorf("filetype filter can only be applied to the syscall exit")
}
filetype, err := getFiletype(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, uint32(filetype))
case arg0Field, arg1Field, arg2Field, arg3Field:
// Convert RHS to a number.
arg, err := parseNum(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, arg)
// case SessionIDField:
case inodeField:
// Flag must be FilterExit.
if rule.flags != exitFilter {
return fmt.Errorf("inode filter can only be applied to the syscall exit")
}
// Comparator must be = or !=.
if op != equalOperator && op != notEqualOperator {
return fmt.Errorf("inode only supports the = and != operators")
}
// Convert RHS to number.
inode, err := parseNum(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, inode)
case saddrFamField:
// Convert RHS to number.
num, err := parseNum(rhs)
if err != nil {
return err
}
const (
// include/linux/socket.h
afInet = 2
afInet6 = 10
)
switch num {
case afInet, afInet6:
default:
return fmt.Errorf("saddr_fam must be 2 or 10: have %d", num)
}
rule.values = append(rule.values, num)
case devMajorField, devMinorField, successField, ppidField:
// Flag must be FilterExit.
if rule.flags != exitFilter {
return fmt.Errorf("%v filter can only be applied to the syscall exit", lhs)
}
fallthrough
default:
// Convert RHS to number.
num, err := parseNum(rhs)
if err != nil {
return err
}
rule.values = append(rule.values, num)
}
rule.fields = append(rule.fields, field)
rule.fieldFlags = append(rule.fieldFlags, op)
return nil
}
func getUID(uid string) (uint32, error) {
if uid == "unset" || uid == "-1" {
return 4294967295, nil
}
v, err := strconv.ParseUint(uid, 10, 32)
if err != nil {
if !errors.Is(err, strconv.ErrSyntax) {
return 0, fmt.Errorf("failed to parse uid '%v': %w", uid, err)
}
u, err := user.Lookup(uid)
if err != nil {
return 0, fmt.Errorf("failed to convert user '%v' to a numeric ID: %w", uid, err)
}
v, err = strconv.ParseUint(u.Uid, 10, 32)
if err != nil {
return 0, fmt.Errorf("failed to parse uid '%v' belonging to user '%v': %w", u.Uid, u.Username, err)
}
}
return uint32(v), nil
}
func getGID(gid string) (uint32, error) {
v, err := strconv.ParseUint(gid, 10, 32)
if err != nil {
if !errors.Is(err, strconv.ErrSyntax) {
return 0, fmt.Errorf("failed to parse gid '%v': %w", gid, err)
}
g, err := user.LookupGroup(gid)
if err != nil {
return 0, fmt.Errorf("failed to convert group '%v' to a numeric ID: %w", gid, err)
}
v, err = strconv.ParseUint(g.Gid, 10, 32)
if err != nil {
return 0, fmt.Errorf("failed to parse gid '%v' belonging to group '%v': %w", g.Gid, g.Name, err)
}
}
return uint32(v), nil
}
func getExitCode(exit string) (int32, error) {
v, err := strconv.ParseInt(exit, 0, 32)
if err != nil {
if !errors.Is(err, strconv.ErrSyntax) {
return 0, fmt.Errorf("failed to parse exit code '%v': %w", exit, err)
}
sign := 1
code := exit
if strings.HasPrefix(exit, "-") {
sign = -1
code = exit[1:]
}
num, found := auparse.AuditErrnoToNum[code]
if !found {
return 0, fmt.Errorf("failed to convert error to exit code '%v'", exit)
}
v = int64(sign * num)
}
return int32(v), nil
}
func getArch(arch string) (string, uint32, error) {
realArch := arch
switch strings.ToLower(arch) {
case "b64":
runtimeArch, err := getRuntimeArch()
if err != nil {
return "", 0, err
}
switch runtimeArch {
case "aarch64", "x86_64", "ppc64", "ppc64le", "s390x":
realArch = runtimeArch
default:
return "", 0, fmt.Errorf("cannot use b64 on %v", runtimeArch)
}
case "b32":
runtimeArch, err := getRuntimeArch()
if err != nil {
return "", 0, err
}
switch runtimeArch {
case "arm", "i386", "s390":
realArch = runtimeArch
case "aarch64":
realArch = "arm"
case "x86_64":
realArch = "i386"
case "ppc64", "ppc64le":
realArch = "ppc"
case "s390x":
realArch = "s390"
default:
return "", 0, fmt.Errorf("cannot use b32 on %v", runtimeArch)
}
}
archValue, found := reverseArch[realArch]
if !found {
return "", 0, fmt.Errorf("unknown arch '%v'", arch)
}
return realArch, archValue, nil
}
// from a rule arch returned by kernel, decide what arch name to display
func getDisplayArch(archID uint32) (string, error) {
runtimeArchStr, err := getRuntimeArch()
if err != nil {
return "", err
}
runtimeArchU32, ok := reverseArch[runtimeArchStr]
if !ok {
return "", errors.New("current architecture not supported")
}
runtimeArch := auparse.AuditArch(runtimeArchU32)
requestedArch := auparse.AuditArch(archID)
if requestedArch == runtimeArch {
switch requestedArch {
case auparse.AUDIT_ARCH_AARCH64, auparse.AUDIT_ARCH_X86_64, auparse.AUDIT_ARCH_PPC64, auparse.AUDIT_ARCH_S390X:
return "b64", nil
case auparse.AUDIT_ARCH_ARM, auparse.AUDIT_ARCH_I386, auparse.AUDIT_ARCH_PPC, auparse.AUDIT_ARCH_S390:
return "b32", nil
}
} else {
switch {
case runtimeArch == auparse.AUDIT_ARCH_AARCH64 && requestedArch == auparse.AUDIT_ARCH_ARM,
runtimeArch == auparse.AUDIT_ARCH_X86_64 && requestedArch == auparse.AUDIT_ARCH_I386,
runtimeArch == auparse.AUDIT_ARCH_PPC64 && requestedArch == auparse.AUDIT_ARCH_PPC,
runtimeArch == auparse.AUDIT_ARCH_S390X && requestedArch == auparse.AUDIT_ARCH_S390:
return "b32", nil
}
}
name, ok := auparse.AuditArchNames[requestedArch]
if !ok {
return "", fmt.Errorf("unsupported arch=%x in rule", requestedArch)
}
return name, nil
}
// getRuntimeArch returns the program's arch (not the machine's arch).
func getRuntimeArch() (string, error) {
var arch string
switch runtime.GOARCH {
case "arm":
arch = "arm"
case "arm64":
arch = "aarch64"
case "386":
arch = "i386"
case "amd64":
arch = "x86_64"
case "ppc64", "ppc64le", "ppc":
arch = runtime.GOARCH
case "s390":
arch = "s390"
case "s390x":
arch = "s390x"
case "mips", "mipsle", "mips64", "mips64le":
fallthrough
default:
return "", fmt.Errorf("unsupported arch: %v", runtime.GOARCH)
}
return arch, nil
}
func getAuditMsgType(msgType string) (uint32, error) {
v, err := strconv.ParseUint(msgType, 0, 32)
if err != nil {
if !errors.Is(err, strconv.ErrSyntax) {
return 0, fmt.Errorf("failed to parse msgtype '%v': %w", msgType, err)
}
typ, err := auparse.GetAuditMessageType(msgType)
if err != nil {
return 0, fmt.Errorf("failed to convert msgtype '%v' to numeric value: %w", msgType, err)
}
v = uint64(typ)
}
return uint32(v), nil
}
func getPerm(perm string) (uint32, error) {
var permBits permission
for _, p := range perm {
switch p {
case 'r':
permBits |= readPerm
case 'w':
permBits |= writePerm
case 'x':
permBits |= execPerm
case 'a':
permBits |= attrPerm
default:
return 0, fmt.Errorf("invalid permission access type '%v'", p)
}
}
return uint32(permBits), nil
}
// String returns the string representation of the permission bits.
func (bits permission) String() string {
perms := make([]byte, 0, 4)
if bits&readPerm != 0 {
perms = append(perms, 'r')
}
if bits&writePerm != 0 {
perms = append(perms, 'w')
}
if bits&execPerm != 0 {
perms = append(perms, 'x')
}
if bits&attrPerm != 0 {
perms = append(perms, 'a')
}
return string(perms)
}
func getFiletype(filetype string) (filetype, error) {
switch strings.ToLower(filetype) {
case "file":
return fileFiletype, nil
case "dir":
return dirFiletype, nil
case "socket":
return socketFiletype, nil
case "symlink":
return linkFiletype, nil
case "char":
return characterFiletype, nil
case "block":
return blockFiletype, nil
case "fifo":
return fifoFiletype, nil
default:
return 0, fmt.Errorf("invalid filetype '%v'", filetype)
}
}
// String returns the string representation of a filetype
func (ft filetype) String() string {
switch ft {
case fileFiletype:
return "file"
case dirFiletype:
return "dir"
case socketFiletype:
return "socket"
case linkFiletype:
return "symlink"
case characterFiletype:
return "char"
case blockFiletype:
return "block"
case fifoFiletype:
return "fifo"
default:
return fmt.Sprintf("UNKNOWN:%x", uint32(ft))
}
}
func parseNum(num string) (uint32, error) {
if strings.HasPrefix(num, "-") {
v, err := strconv.ParseInt(num, 0, 32)
return uint32(v), err
}
v, err := strconv.ParseUint(num, 0, 32)
return uint32(v), err
}