in rule/rule.go [106:319]
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
}