Skip to content

Commit c81be52

Browse files
committed
audit: fix a race condition with the auditd tracking code
Originally reported by Adam and Dusty, it appears we have a small race window in kauditd_thread(), as documented in the Fedora BZ: * https://bugzilla.redhat.com/show_bug.cgi?id=1459326#c35 "This issue is partly due to the read-copy nature of RCU, and partly due to how we sync the auditd_connection state across kauditd_thread and the audit control channel. The kauditd_thread thread is always running so it can service the record queues and emit the multicast messages, if it happens to be just past the "main_queue" label, but before the "if (sk == NULL || ...)" if-statement which calls auditd_reset() when the new auditd connection is registered it could end up resetting the auditd connection, regardless of if it is valid or not. This is a rather small window and the variable nature of multi-core scheduling explains why this is proving rather difficult to reproduce." The fix is to have functions only call auditd_reset() when they believe that the kernel/auditd connection is still valid, e.g. non-NULL, and to have these callers pass their local copy of the auditd_connection pointer to auditd_reset() where it can be compared with the current connection state before resetting. If the caller has a stale state tracking pointer then the reset is ignored. We also make a small change to kauditd_thread() so that if the kernel/auditd connection is dead we skip the retry queue and send the records straight to the hold queue. This is necessary as we used to rely on auditd_reset() to occasionally purge the retry queue but we are going to be calling the reset function much less now and we want to make sure the retry queue doesn't grow unbounded. Reported-by: Adam Williamson <[email protected]> Reported-by: Dusty Mabe <[email protected]> Reviewed-by: Richard Guy Briggs <[email protected]> Signed-off-by: Paul Moore <[email protected]>
1 parent e4c1a0d commit c81be52

File tree

1 file changed

+23
-13
lines changed

1 file changed

+23
-13
lines changed

kernel/audit.c

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -575,12 +575,16 @@ static void kauditd_retry_skb(struct sk_buff *skb)
575575

576576
/**
577577
* auditd_reset - Disconnect the auditd connection
578+
* @ac: auditd connection state
578579
*
579580
* Description:
580581
* Break the auditd/kauditd connection and move all the queued records into the
581-
* hold queue in case auditd reconnects.
582+
* hold queue in case auditd reconnects. It is important to note that the @ac
583+
* pointer should never be dereferenced inside this function as it may be NULL
584+
* or invalid, you can only compare the memory address! If @ac is NULL then
585+
* the connection will always be reset.
582586
*/
583-
static void auditd_reset(void)
587+
static void auditd_reset(const struct auditd_connection *ac)
584588
{
585589
unsigned long flags;
586590
struct sk_buff *skb;
@@ -590,6 +594,11 @@ static void auditd_reset(void)
590594
spin_lock_irqsave(&auditd_conn_lock, flags);
591595
ac_old = rcu_dereference_protected(auditd_conn,
592596
lockdep_is_held(&auditd_conn_lock));
597+
if (ac && ac != ac_old) {
598+
/* someone already registered a new auditd connection */
599+
spin_unlock_irqrestore(&auditd_conn_lock, flags);
600+
return;
601+
}
593602
rcu_assign_pointer(auditd_conn, NULL);
594603
spin_unlock_irqrestore(&auditd_conn_lock, flags);
595604

@@ -649,8 +658,8 @@ static int auditd_send_unicast_skb(struct sk_buff *skb)
649658
return rc;
650659

651660
err:
652-
if (rc == -ECONNREFUSED)
653-
auditd_reset();
661+
if (ac && rc == -ECONNREFUSED)
662+
auditd_reset(ac);
654663
return rc;
655664
}
656665

@@ -795,32 +804,33 @@ static int kauditd_thread(void *dummy)
795804
rc = kauditd_send_queue(sk, portid,
796805
&audit_hold_queue, UNICAST_RETRIES,
797806
NULL, kauditd_rehold_skb);
798-
if (rc < 0) {
807+
if (ac && rc < 0) {
799808
sk = NULL;
800-
auditd_reset();
809+
auditd_reset(ac);
801810
goto main_queue;
802811
}
803812

804813
/* attempt to flush the retry queue */
805814
rc = kauditd_send_queue(sk, portid,
806815
&audit_retry_queue, UNICAST_RETRIES,
807816
NULL, kauditd_hold_skb);
808-
if (rc < 0) {
817+
if (ac && rc < 0) {
809818
sk = NULL;
810-
auditd_reset();
819+
auditd_reset(ac);
811820
goto main_queue;
812821
}
813822

814823
main_queue:
815824
/* process the main queue - do the multicast send and attempt
816825
* unicast, dump failed record sends to the retry queue; if
817826
* sk == NULL due to previous failures we will just do the
818-
* multicast send and move the record to the retry queue */
827+
* multicast send and move the record to the hold queue */
819828
rc = kauditd_send_queue(sk, portid, &audit_queue, 1,
820829
kauditd_send_multicast_skb,
821-
kauditd_retry_skb);
822-
if (sk == NULL || rc < 0)
823-
auditd_reset();
830+
(sk ?
831+
kauditd_retry_skb : kauditd_hold_skb));
832+
if (ac && rc < 0)
833+
auditd_reset(ac);
824834
sk = NULL;
825835

826836
/* drop our netns reference, no auditd sends past this line */
@@ -1230,7 +1240,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
12301240
auditd_pid, 1);
12311241

12321242
/* unregister the auditd connection */
1233-
auditd_reset();
1243+
auditd_reset(NULL);
12341244
}
12351245
}
12361246
if (s.mask & AUDIT_STATUS_RATE_LIMIT) {

0 commit comments

Comments
 (0)