Hi,

To cache lookups, the policy ipo is linked to its SA tdb.  There
is a list of SAs that belong to a policy.  To make it MP safe we
need a mutex around these pointers.

Hrvoje: Can you test this alone and together with the ip forwarding
diff.  I protects the data structure where the latter crashed.
Wonder if this helps.

ok?

bluhm

Index: net/pfkeyv2.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/net/pfkeyv2.c,v
retrieving revision 1.227
diff -u -p -r1.227 pfkeyv2.c
--- net/pfkeyv2.c       8 Dec 2021 14:24:18 -0000       1.227
+++ net/pfkeyv2.c       10 Dec 2021 17:26:25 -0000
@@ -2004,12 +2004,15 @@ pfkeyv2_send(struct socket *so, void *me
                                (caddr_t)&ipo->ipo_mask, rnh,
                                ipo->ipo_nodes, 0)) == NULL) {
                                /* Remove from linked list of policies on TDB */
+                               mtx_enter(&ipo_tdb_mtx);
                                if (ipo->ipo_tdb != NULL) {
                                        TAILQ_REMOVE(
                                            &ipo->ipo_tdb->tdb_policy_head,
                                            ipo, ipo_tdb_next);
                                        tdb_unref(ipo->ipo_tdb);
+                                       ipo->ipo_tdb = NULL;
                                }
+                               mtx_leave(&ipo_tdb_mtx);
                                if (ipo->ipo_ids)
                                        ipsp_ids_free(ipo->ipo_ids);
                                pool_put(&ipsec_policy_pool, ipo);
Index: netinet/ip_ipsp.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/ip_ipsp.c,v
retrieving revision 1.263
diff -u -p -r1.263 ip_ipsp.c
--- netinet/ip_ipsp.c   8 Dec 2021 14:24:18 -0000       1.263
+++ netinet/ip_ipsp.c   10 Dec 2021 17:26:25 -0000
@@ -926,12 +926,14 @@ tdb_cleanspd(struct tdb *tdbp)
 {
        struct ipsec_policy *ipo;
 
+       mtx_enter(&ipo_tdb_mtx);
        while ((ipo = TAILQ_FIRST(&tdbp->tdb_policy_head)) != NULL) {
                TAILQ_REMOVE(&tdbp->tdb_policy_head, ipo, ipo_tdb_next);
                tdb_unref(ipo->ipo_tdb);
                ipo->ipo_tdb = NULL;
                ipo->ipo_last_searched = 0; /* Force a re-search. */
        }
+       mtx_leave(&ipo_tdb_mtx);
 }
 
 void
Index: netinet/ip_ipsp.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/ip_ipsp.h,v
retrieving revision 1.229
diff -u -p -r1.229 ip_ipsp.h
--- netinet/ip_ipsp.h   8 Dec 2021 14:24:18 -0000       1.229
+++ netinet/ip_ipsp.h   10 Dec 2021 17:26:25 -0000
@@ -257,6 +257,10 @@ struct ipsec_acquire {
        TAILQ_ENTRY(ipsec_acquire)      ipa_next;
 };
 
+/*
+ * Locks used to protect struct members in this file:
+ *     p       ipo_tdb_mtx             link policy to TDB global mutex
+ */
 struct ipsec_policy {
        struct radix_node       ipo_nodes[2];   /* radix tree glue */
        struct sockaddr_encap   ipo_addr;
@@ -274,7 +278,7 @@ struct ipsec_policy {
                                                 * mode was used.
                                                 */
 
-       u_int64_t               ipo_last_searched;      /* Timestamp of last 
lookup */
+       u_int64_t       ipo_last_searched;      /* [p] Timestamp of lookup */
 
        u_int8_t                ipo_flags;      /* See IPSP_POLICY_* 
definitions */
        u_int8_t                ipo_type;       /* USE/ACQUIRE/... */
@@ -283,7 +287,7 @@ struct ipsec_policy {
 
        int                     ipo_ref_count;
 
-       struct tdb              *ipo_tdb;               /* Cached entry */
+       struct tdb              *ipo_tdb;       /* [p] Cached TDB entry */
 
        struct ipsec_ids        *ipo_ids;
 
@@ -313,7 +317,8 @@ struct ipsec_policy {
  * Locks used to protect struct members in this file:
  *     I       immutable after creation
  *     N       net lock
- *     s       tdb_sadb_mtx
+ *     p       ipo_tdb_mtx             link policy to TDB global mutex
+ *     s       tdb_sadb_mtx            SA database global mutex
  */
 struct tdb {                           /* tunnel descriptor block */
        /*
@@ -436,7 +441,7 @@ struct tdb {                                /* tunnel 
descriptor blo
        struct sockaddr_encap   tdb_filter; /* What traffic is acceptable */
        struct sockaddr_encap   tdb_filtermask; /* And the mask */
 
-       TAILQ_HEAD(tdb_policy_head, ipsec_policy)       tdb_policy_head;
+       TAILQ_HEAD(tdb_policy_head, ipsec_policy) tdb_policy_head; /* [p] */
        TAILQ_ENTRY(tdb)        tdb_sync_entry;
 };
 #define tdb_ipackets           tdb_data.tdd_ipackets
@@ -544,6 +549,7 @@ extern char ipsec_def_comp[];
 extern TAILQ_HEAD(ipsec_policy_head, ipsec_policy) ipsec_policy_head;
 
 extern struct mutex tdb_sadb_mtx;
+extern struct mutex ipo_tdb_mtx;
 
 struct cryptop;
 
Index: netinet/ip_spd.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/ip_spd.c,v
retrieving revision 1.108
diff -u -p -r1.108 ip_spd.c
--- netinet/ip_spd.c    3 Dec 2021 17:18:34 -0000       1.108
+++ netinet/ip_spd.c    10 Dec 2021 23:08:54 -0000
@@ -53,6 +53,12 @@ void ipsp_delete_acquire(struct ipsec_ac
 struct pool ipsec_policy_pool;
 struct pool ipsec_acquire_pool;
 
+/*
+ * For tdb_walk() calling tdb_delete_locked() we need lock order
+ * tdb_sadb_mtx before ipo_tdb_mtx.
+ */
+struct mutex ipo_tdb_mtx = MUTEX_INITIALIZER(IPL_SOFTNET);
+
 /* Protected by the NET_LOCK(). */
 struct radix_node_head **spd_tables;
 unsigned int spd_table_max;
@@ -360,6 +366,7 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
        }
 
        /* Do we have a cached entry ? If so, check if it's still valid. */
+       mtx_enter(&ipo_tdb_mtx);
        if (ipo->ipo_tdb != NULL &&
            (ipo->ipo_tdb->tdb_flags & TDBF_INVALID)) {
                TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo,
@@ -367,6 +374,7 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
                tdb_unref(ipo->ipo_tdb);
                ipo->ipo_tdb = NULL;
        }
+       mtx_leave(&ipo_tdb_mtx);
 
        /* Outgoing packet policy check. */
        if (direction == IPSP_DIRECTION_OUT) {
@@ -393,6 +401,7 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
                        ids = ipsp_ids_lookup(ipsecflowinfo);
 
                /* Check that the cached TDB (if present), is appropriate. */
+               mtx_enter(&ipo_tdb_mtx);
                if (ipo->ipo_tdb != NULL) {
                        if ((ipo->ipo_last_searched <= ipsec_last_added) ||
                            (ipo->ipo_sproto != ipo->ipo_tdb->tdb_sproto) ||
@@ -407,7 +416,9 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
                                goto nomatchout;
 
                        /* Cached entry is good. */
-                       return ipsp_spd_inp(m, inp, ipo, tdbout);
+                       error = ipsp_spd_inp(m, inp, ipo, tdbout);
+                       mtx_leave(&ipo_tdb_mtx);
+                       return error;
 
   nomatchout:
                        /* Cached TDB was not good. */
@@ -428,23 +439,38 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
                 * explosion in the number of acquired SAs).
                 */
                if (ipo->ipo_last_searched <= ipsec_last_added) {
+                       struct tdb *tdbp_new;
+
                        /* "Touch" the entry. */
                        if (dignore == 0)
                                ipo->ipo_last_searched = getuptime();
 
+                       /* gettdb() takes tdb_sadb_mtx, preserve lock order */
+                       mtx_leave(&ipo_tdb_mtx);
                        /* Find an appropriate SA from the existing ones. */
-                       ipo->ipo_tdb = gettdbbydst(rdomain,
+                       tdbp_new = gettdbbydst(rdomain,
                            dignore ? &sdst : &ipo->ipo_dst,
                            ipo->ipo_sproto, ids ? ids: ipo->ipo_ids,
                            &ipo->ipo_addr, &ipo->ipo_mask);
+                       mtx_enter(&ipo_tdb_mtx);
+                       if (ipo->ipo_tdb != NULL) {
+                               /* Remove cached TDB from parallel thread. */
+                               TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head,
+                                   ipo, ipo_tdb_next);
+                               tdb_unref(ipo->ipo_tdb);
+                       }
+                       ipo->ipo_tdb = tdbp_new;
                        if (ipo->ipo_tdb != NULL) {
                                /* gettdbbydst() has already refcounted tdb */
                                TAILQ_INSERT_TAIL(
                                    &ipo->ipo_tdb->tdb_policy_head,
                                    ipo, ipo_tdb_next);
-                               return ipsp_spd_inp(m, inp, ipo, tdbout);
+                               error = ipsp_spd_inp(m, inp, ipo, tdbout);
+                               mtx_leave(&ipo_tdb_mtx);
+                               return error;
                        }
                }
+               mtx_leave(&ipo_tdb_mtx);
 
                /* So, we don't have an SA -- just a policy. */
                switch (ipo->ipo_type) {
@@ -490,8 +516,13 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
                                tdbp = tdbp->tdb_inext;
 
                        /* Direct match in the cache. */
-                       if (ipo->ipo_tdb == tdbp)
-                               return ipsp_spd_inp(m, inp, ipo, tdbout);
+                       mtx_enter(&ipo_tdb_mtx);
+                       if (ipo->ipo_tdb == tdbp) {
+                               error = ipsp_spd_inp(m, inp, ipo, tdbout);
+                               mtx_leave(&ipo_tdb_mtx);
+                               return error;
+                       }
+                       mtx_leave(&ipo_tdb_mtx);
 
                        if (memcmp(dignore ? &ssrc : &ipo->ipo_dst,
                            &tdbp->tdb_src, tdbp->tdb_src.sa.sa_len) ||
@@ -505,6 +536,7 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
                                        goto nomatchin;
 
                        /* Add it to the cache. */
+                       mtx_enter(&ipo_tdb_mtx);
                        if (ipo->ipo_tdb != NULL) {
                                TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head,
                                    ipo, ipo_tdb_next);
@@ -513,13 +545,16 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
                        ipo->ipo_tdb = tdb_ref(tdbp);
                        TAILQ_INSERT_TAIL(&tdbp->tdb_policy_head, ipo,
                            ipo_tdb_next);
-                       return ipsp_spd_inp(m, inp, ipo, tdbout);
+                       error = ipsp_spd_inp(m, inp, ipo, tdbout);
+                       mtx_leave(&ipo_tdb_mtx);
+                       return error;
 
   nomatchin: /* Nothing needed here, falling through */
        ;
                }
 
                /* Check whether cached entry applies. */
+               mtx_enter(&ipo_tdb_mtx);
                if (ipo->ipo_tdb != NULL) {
                        /*
                         * We only need to check that the correct
@@ -543,13 +578,25 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
 
                /* Find whether there exists an appropriate SA. */
                if (ipo->ipo_last_searched <= ipsec_last_added) {
+                       struct tdb *tdbp_new;
+
                        if (dignore == 0)
                                ipo->ipo_last_searched = getuptime();
 
-                       ipo->ipo_tdb = gettdbbysrc(rdomain,
+                       /* gettdb() takes tdb_sadb_mtx, preserve lock order */
+                       mtx_leave(&ipo_tdb_mtx);
+                       tdbp_new = gettdbbysrc(rdomain,
                            dignore ? &ssrc : &ipo->ipo_dst,
                            ipo->ipo_sproto, ipo->ipo_ids,
                            &ipo->ipo_addr, &ipo->ipo_mask);
+                       mtx_enter(&ipo_tdb_mtx);
+                       if (ipo->ipo_tdb != NULL) {
+                               /* Remove cached TDB from parallel thread. */
+                               TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head,
+                                   ipo, ipo_tdb_next);
+                               tdb_unref(ipo->ipo_tdb);
+                       }
+                       ipo->ipo_tdb = tdbp_new;
                        if (ipo->ipo_tdb != NULL) {
                                /* gettdbbysrc() has already refcounted tdb */
                                TAILQ_INSERT_TAIL(
@@ -558,6 +605,7 @@ ipsp_spd_lookup(struct mbuf *m, int af, 
                        }
                }
   skipinputsearch:
+               mtx_leave(&ipo_tdb_mtx);
 
                switch (ipo->ipo_type) {
                case IPSP_IPSEC_REQUIRE:
@@ -615,12 +663,14 @@ ipsec_delete_policy(struct ipsec_policy 
            rn_delete(&ipo->ipo_addr, &ipo->ipo_mask, rnh, rn) == NULL)
                return (ESRCH);
 
+       mtx_enter(&ipo_tdb_mtx);
        if (ipo->ipo_tdb != NULL) {
                TAILQ_REMOVE(&ipo->ipo_tdb->tdb_policy_head, ipo,
                    ipo_tdb_next);
                tdb_unref(ipo->ipo_tdb);
                ipo->ipo_tdb = NULL;
        }
+       mtx_leave(&ipo_tdb_mtx);
 
        while ((ipa = TAILQ_FIRST(&ipo->ipo_acquires)) != NULL)
                ipsp_delete_acquire(ipa);
@@ -825,10 +875,9 @@ ipsp_spd_inp(struct mbuf *m, struct inpc
 
  justreturn:
        if (tdbout != NULL) {
-               if (ipo != NULL) {
-                       tdb_ref(ipo->ipo_tdb);
-                       *tdbout = ipo->ipo_tdb;
-               } else
+               if (ipo != NULL)
+                       *tdbout = tdb_ref(ipo->ipo_tdb);
+               else
                        *tdbout = NULL;
        }
        return 0;

Reply via email to