test patch for auditctl inter-field comparisons on euid/uid, egid/gid

Peter Moody pmoody at google.com
Sun Dec 11 19:09:27 UTC 2011


This patch extends Eric's test patch from 11/17 (
http://www.redhat.com/archives/linux-audit/2011-November/msg00045.html).
This turns -C into a long opt with similar syntax to -F.

This allows uid/euid and gid/egid to be compared, like

auditctl -a exit,always -F arch=b64 -C 'euid!=uid' -S execve -F 'euid!=0'
-F 'success=1'

which would audit on someone executing a setuid binary if the binary isn't
setuid root.

You can also check for writes to overly permissive files like

auditctl -a exit,always -F arch=b64 -C 'obj_uid!=uid' -F 'uid!=0' -F
'dir=/home/' -F 'success=1' -S open -F 'a2&=2'

This functionality is helpful in detecting user compromises across a shared
fleet; eg, attacker finds a world-writable shell script
(/home/victim/.bashrc, it's happened...) and inserts "cp /bin/bash /tmp/;
chmod 7777 /tmp/bash". After victim executes this, attacker executes
/tmp/bash -p and becomes victim.

One strange thing related to this patch: auditd seems to be reporting
success for a normal user process (gklrellm) opening /proc/meminfo (mode
444) O_RDWR, and I don't see how this is possible.  eg:

type=SYSCALL msg=audit(1323540255.146:97): arch=c000003e syscall=2
success=yes exit=13 a0=4b1972 a1=0 a2=1b6 a3=0 items=1 ppid=1704 pid=1797
auid=11532 uid=11532 gid=5000 euid=11532 suid=11532 fsuid=11532 egid=5000
sgid=5000 fsgid=5000 tty=(none) ses=1 comm="gkrellm" exe="/usr/bin/gkrellm"
key="permissive"
type=CWD msg=audit(1323540255.146:97):  cwd="/home/pmoody"
type=PATH msg=audit(1323540255.146:97): item=0 name="/proc/meminfo" inode=
4026532008 dev=00:03 mode=0100444 ouid=0 ogid=0 rdev=00:00

hopefully someone with more auditd internal knowledge can explain what's
going on.

auditctl -l doesn't know how to report this yet; if this patch is generally
acceptable, I can try to fix that and update the manpage, etc.

Signed-off-by: Peter Moody <pmoody at google.com>
---
 trunk/auparse/typetab.h     |    1 +
 trunk/lib/fieldtab.h        |    1 +
 trunk/lib/libaudit.c        |  144
+++++++++++++++++++++++++++++++++++++++++++
 trunk/lib/libaudit.h        |    2 +
 trunk/src/auditctl.c        |   19 +++++-
 trunk/src/ausearch-report.c |    1 +
 6 files changed, 166 insertions(+), 2 deletions(-)

diff --git a/trunk/auparse/typetab.h b/trunk/auparse/typetab.h
index 746573c..3e6c6d1 100644
--- a/trunk/auparse/typetab.h
+++ b/trunk/auparse/typetab.h
@@ -32,6 +32,7 @@ _S(AUPARSE_TYPE_UID, "iuid" )
 _S(AUPARSE_TYPE_UID, "id" )
 _S(AUPARSE_TYPE_UID, "inode_uid" )
 _S(AUPARSE_TYPE_UID, "sauid" )
+_S(AUPARSE_TYPE_UID, "obj_uid" )
 _S(AUPARSE_TYPE_GID, "gid" )
 _S(AUPARSE_TYPE_GID, "egid" )
 _S(AUPARSE_TYPE_GID, "sgid" )
diff --git a/trunk/lib/fieldtab.h b/trunk/lib/fieldtab.h
index ad95814..e053df6 100644
--- a/trunk/lib/fieldtab.h
+++ b/trunk/lib/fieldtab.h
@@ -55,6 +55,7 @@ _S(AUDIT_WATCH,        "path"         )
 _S(AUDIT_PERM,         "perm"         )
 _S(AUDIT_DIR,          "dir"          )
 _S(AUDIT_FILETYPE,     "filetype"     )
+_S(AUDIT_OBJ_UID,      "obj_uid"      )

 _S(AUDIT_ARG0,         "a0"           )
 _S(AUDIT_ARG1,         "a1"           )
diff --git a/trunk/lib/libaudit.c b/trunk/lib/libaudit.c
index 9a5070c..b10f984 100644
--- a/trunk/lib/libaudit.c
+++ b/trunk/lib/libaudit.c
@@ -783,6 +783,148 @@ int audit_rule_syscallbyname_data(struct
audit_rule_data *rule,
 }
 hidden_def(audit_rule_syscallbyname_data)

+int audit_rule_interfield_fieldpair_data(struct audit_rule_data **rulep,
+ const char *pair,
+ int flags) {
+ const char *f = pair;
+ char       *v;
+ int        op;
+ int        field1, field2;
+ int        vlen;
+ int        offset;
+ struct audit_rule_data *rule = *rulep;
+
+ if (f == NULL)
+ return -1;
+
+ /* look for 2-char operators first
+   then look for 1-char operators afterwards
+   when found, null out the bytes under the operators to split
+   and set value pointer just past operator bytes
+ */
+ if ( (v = strstr(pair, "!=")) ) {
+ *v++ = '\0';
+ *v++ = '\0';
+ op = AUDIT_NOT_EQUAL;
+ } else if ( (v = strstr(pair, "=")) ) {
+ *v++ = '\0';
+ op = AUDIT_EQUAL;
+ } else {
+ fprintf(stderr, "only =, != comparisons are allowed in interfield\n");
+ return -1;
+ }
+
+ if (v == NULL)
+ return -1;
+
+ if (*f == 0)
+ return -22;
+
+ if (*v == 0)
+ return -20;
+
+ if ((field1 = audit_name_to_field(f)) < 0)
+ return -2;
+
+ if ((field2 = audit_name_to_field(v)) < 0)
+ return -2;
+
+ /* Exclude filter can be used only with MSGTYPE field */
+ if (flags == AUDIT_FILTER_EXCLUDE && field1 != AUDIT_MSGTYPE)
+ return -12;
+
+ // It should always be AUDIT_FIELD_COMPARE
+ rule->fields[rule->field_count] = AUDIT_FIELD_COMPARE;
+ rule->fieldflags[rule->field_count] = op;
+ switch (field1)
+ {
+ case AUDIT_UID:
+ switch(field2) {
+ case AUDIT_EUID:
+ rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_EUID;
+ break;
+ case AUDIT_OBJ_UID:
+ rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_OBJ_UID;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ case AUDIT_EUID:
+ switch(field2) {
+ case AUDIT_UID:
+ rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_EUID;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ case AUDIT_OBJ_UID:
+ switch(field2) {
+ case AUDIT_UID:
+ rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_OBJ_UID;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ case AUDIT_OBJ_GID:
+ switch(field2) {
+ case AUDIT_GID:
+ rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_OBJ_GID;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ case AUDIT_GID:
+ switch(field2) {
+ case AUDIT_EGID:
+ rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_EGID;
+ break;
+ case AUDIT_OBJ_GID:
+ rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_OBJ_GID;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ case AUDIT_EGID:
+ switch(field2) {
+ case AUDIT_OBJ_GID:
+ rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_EGID;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ /* fallthrough */
+ default:
+ if (field1 == AUDIT_INODE) {
+ if (!(op == AUDIT_NOT_EQUAL ||
+ op == AUDIT_EQUAL))
+ return -13;
+ }
+
+ if (field1 == AUDIT_PPID && !(flags == AUDIT_FILTER_EXIT
+ || flags == AUDIT_FILTER_ENTRY))
+ return -17;
+
+ if (!isdigit((char)*(v)))
+ return -21;
+
+ if (field1 == AUDIT_INODE)
+ rule->values[rule->field_count] =
+ strtoul(v, NULL, 0);
+ else
+ rule->values[rule->field_count] =
+ strtol(v, NULL, 0);
+ break;
+ }
+ rule->field_count++;
+ return 0;
+}
+
 int audit_rule_fieldpair_data(struct audit_rule_data **rulep, const char
*pair,
                               int flags)
 {
@@ -857,6 +999,8 @@ int audit_rule_fieldpair_data(struct audit_rule_data
**rulep, const char *pair,
  case AUDIT_SUID:
  case AUDIT_FSUID:
  case AUDIT_LOGINUID:
+ case AUDIT_OBJ_UID:
+ case AUDIT_OBJ_GID:
  // Do positive & negative separate for 32 bit systems
  vlen = strlen(v);
  if (isdigit((char)*(v)))
diff --git a/trunk/lib/libaudit.h b/trunk/lib/libaudit.h
index 8feaa39..911bce4 100644
--- a/trunk/lib/libaudit.h
+++ b/trunk/lib/libaudit.h
@@ -428,6 +428,8 @@ extern int  audit_rule_syscallbyname_data(struct
audit_rule_data *rule,
  * adding new fields */
 extern int  audit_rule_fieldpair_data(struct audit_rule_data **rulep,
                                       const char *pair, int flags);
+extern int audit_rule_interfield_fieldpair_data(struct audit_rule_data
**rulep,
+ const char *pair, int flags);
 extern void audit_rule_free_data(struct audit_rule_data *rule);

 #ifdef __cplusplus
diff --git a/trunk/src/auditctl.c b/trunk/src/auditctl.c
index 34b7935..d7ec998 100644
--- a/trunk/src/auditctl.c
+++ b/trunk/src/auditctl.c
@@ -482,7 +482,7 @@ static int setopt(int count, int lineno, char *vars[])
     keylen = AUDIT_MAX_KEY_LEN;

     while ((retval >= 0) && (c = getopt(count, vars,
- "hislDvte:f:r:b:a:A:d:S:F:m:R:w:W:k:p:q:")) != EOF) {
+ "hislDvtC:e:f:r:b:a:A:d:S:F:m:R:w:W:k:p:q:")) != EOF) {
  int flags = AUDIT_FILTER_UNSET;
  rc = 10; // Init to something impossible to see if unused.
         switch (c) {
@@ -731,7 +731,6 @@ static int setopt(int count, int lineno, char *vars[])
  retval = -1;
  break;
  }
-
  rc = audit_rule_fieldpair_data(&rule_new,optarg,flags);
  if (rc != 0) {
  audit_number_to_errmsg(rc, optarg);
@@ -743,6 +742,22 @@ static int setopt(int count, int lineno, char *vars[])
  }

  break;
+ case 'C':
+ if (add != AUDIT_FILTER_UNSET)
+ flags = add & AUDIT_FILTER_MASK;
+ else if (del != AUDIT_FILTER_UNSET)
+ flags = del & AUDIT_FILTER_MASK;
+
+ rc = audit_rule_interfield_fieldpair_data(&rule_new, optarg, flags);
+ if (rc != 0) {
+ audit_number_to_errmsg(rc, optarg);
+ retval = -1;
+ } else {
+ if (rule_new->fields[rule_new->field_count - 1] ==
+    AUDIT_PERM)
+ audit_permadded = 1;
+ }
+ break;
         case 'm':
  if (count > 3) {
  fprintf(stderr,
diff --git a/trunk/src/ausearch-report.c b/trunk/src/ausearch-report.c
index d50c732..62e1ae0 100644
--- a/trunk/src/ausearch-report.c
+++ b/trunk/src/ausearch-report.c
@@ -333,6 +333,7 @@ static struct nv_pair typetab[] = {
  {T_UID, "id"},
  {T_UID, "inode_uid"},
  {T_UID, "sauid"},
+ {T_UID, "obj_uid"},
  {T_GID, "gid"},
  {T_GID, "egid"},
  {T_GID, "sgid"},
-- 
1.7.3.1


-- 
Peter Moody      Google    1.650.253.7306
Security Engineer  pgp:0xC3410038
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listman.redhat.com/archives/linux-audit/attachments/20111211/614ebd71/attachment.htm>


More information about the Linux-audit mailing list