On Tue, Jun 23, 2020 at 01:00:06AM -0600, Jason A. Donenfeld wrote:
> You can crash a system by running something like:
>
> for i in 1 2 3; do while true; do ifconfig bridge0 create& ifconfig
> bridge0 destroy& done& done
>
> This works with every type of interface I've tried. It appears that
> if_clone_destroy and if_clone_create race with other ioctls, which
> causes a variety of different UaFs or just general logic errors.
>
> One common root cause appears to be that most ifioctl functions use
> ifunit() to find an interface by name, which traverses if_list. Writes
> to if_list are protected by a lock, but reads are apparently
> unprotected. There's also the question of the life time of the object
> returned from ifunit(). Most things that access &ifnet's if_list are
> done without locking, and even if those accesses were to be locked, the
> lock would be released before the object is no longer used, causing the
> UaF in that case as well.
>
> This patch fixes the issue by making if_clone_{create,destroy} exclusive
> with all other ifioctls.
> ---
> sys/net/if.c | 36 +++++++++++++++++++++++++++++++++++-
> 1 file changed, 35 insertions(+), 1 deletion(-)
>
> diff --git sys/net/if.c sys/net/if.c
> index fb2f86f4a7c..9eedea83d32 100644
> --- sys/net/if.c
> +++ sys/net/if.c
> @@ -246,6 +246,11 @@ struct task if_input_task_locked =
> TASK_INITIALIZER(if_netisr, NULL);
> */
> struct rwlock netlock = RWLOCK_INITIALIZER("netlock");
>
> +/*
> + * Protect modifications to objects owned by ifnet's if_list
> + */
> +struct rwlock iflist_obj_lock = RWLOCK_INITIALIZER("iflist_obj_lock");
> +
> /*
> * Network interface utility routines.
> */
> @@ -1905,7 +1910,7 @@ if_setrdomain(struct ifnet *ifp, int rdomain)
> * Interface ioctls.
> */
> int
> -ifioctl(struct socket *so, u_long cmd, caddr_t data, struct proc *p)
> +ifioctl_unlocked(struct socket *so, u_long cmd, caddr_t data, struct proc *p)
> {
> struct ifnet *ifp;
> struct ifreq *ifr = (struct ifreq *)data;
> @@ -2432,6 +2437,35 @@ ifioctl_get(u_long cmd, caddr_t data)
> return (error);
> }
>
> +int
> +ifioctl(struct socket *so, u_long cmd, caddr_t data, struct proc *p)
> +{
> + int ret;
> +
> + switch (cmd) {
> + case SIOCIFCREATE:
> + case SIOCIFDESTROY:
> + rw_enter_write(&iflist_obj_lock);
> + break;
> + default:
> + rw_enter_read(&iflist_obj_lock);
> + }
> +
> + ret = ifioctl_unlocked(so, cmd, data, p);
> +
> + switch (cmd) {
> + case SIOCIFCREATE:
> + case SIOCIFDESTROY:
> + rw_exit_write(&iflist_obj_lock);
> + break;
> + default:
> + rw_exit_read(&iflist_obj_lock);
> + }
> +
> + return (ret);
> +}
> +
> +
> static int
> if_sffpage_check(const caddr_t data)
> {
> --
> 2.27.0
>
As mpi@ pointed you can sleep witchin ifioctl(). In most cases you lock
`iflist_obj_lock' for read and you didn't catch deadlock. But you also
can sleep witchin if_clone_create() and if_clone_create() while you lock
`iflist_obj_lock' for write.
As I see the races are:
1. We sleep in if_clone_create() and we don't mark allocated `unit' as
busy. So we can create multiple `ifp's with identical names and break
ifunit().
2. We sleep in if_clone_destroy() and we don't mark `ifp' as dying. So
we can grab this `ifp' by concurent thread.
3. We don't protect `ifp' being used in other cases so we can destroy
it.
Diff below fixes this issues. It's not for commit and I _know_ it's very
ugly. I just want to show the direction to fix this issus as I see it
and it's quickest way.
1. if_clone_create(). We reserve allocated `unit' unit so we can't do
multiple insertion of `ifp' with idential names.
2. if_clone_destroy(). We mark `ifp' as dying. Also we release `unit'
after we do `ifc_destroy' We can't destroy `ifp' more than once.
3. ifunit() will not receive `ifp' marked as dying.
4. We bump `ifp' ref counter in other cases to be sure if_detach() will
wait all concurent threads.
5. I disabled if_deactivate(ifp); in if_bridge. if_detach() will do it
so there is no reason to do it twice. I will do special diff for this.
I have 12:40 now. I run command below since 10:00 and my system is
still stable.
comments?
---- cut begin ----
for i in 1 2 3; do while true; do ifconfig bridge0 create& \
ifconfig bridge0 destroy& done& done
---- cut end ----
Index: sys/net/if.c
===================================================================
RCS file: /cvs/src/sys/net/if.c,v
retrieving revision 1.610
diff -u -p -r1.610 if.c
--- sys/net/if.c 22 Jun 2020 09:45:13 -0000 1.610
+++ sys/net/if.c 25 Jun 2020 09:30:46 -0000
@@ -1236,6 +1236,18 @@ if_isconnected(const struct ifnet *ifp0,
return (connected);
}
+/* XXX: reserve unit */
+struct if_clone_unit {
+ LIST_ENTRY(if_clone_unit) ifcu_list;
+ struct if_clone *ifcu_ifc;
+ int ifcu_unit;
+};
+
+LIST_HEAD(, if_clone_unit) if_clone_units =
+ LIST_HEAD_INITIALIZER(if_clone_units);
+
+/* XXX: reserve unit */
+
/*
* Create a clone network interface.
*/
@@ -1246,6 +1258,8 @@ if_clone_create(const char *name, int rd
struct ifnet *ifp;
int unit, ret;
+ struct if_clone_unit *ifcu, *tifcu;
+
ifc = if_clone_lookup(name, &unit);
if (ifc == NULL)
return (EINVAL);
@@ -1253,10 +1267,28 @@ if_clone_create(const char *name, int rd
if (ifunit(name) != NULL)
return (EEXIST);
+ /* XXX: reserve unit */
+ ifcu=malloc(sizeof(*ifcu), M_TEMP, M_WAITOK | M_ZERO);
+
+ LIST_FOREACH(tifcu, &if_clone_units, ifcu_list) {
+ if (tifcu->ifcu_ifc == ifc && tifcu->ifcu_unit == unit) {
+ free(ifcu, M_TEMP, 0);
+ return (EEXIST);
+ }
+ }
+ ifcu->ifcu_ifc = ifc;
+ ifcu->ifcu_unit = unit;
+ LIST_INSERT_HEAD(&if_clone_units, ifcu, ifcu_list);
+ /* XXX: reserve unit */
+
+
ret = (*ifc->ifc_create)(ifc, unit);
- if (ret != 0 || (ifp = ifunit(name)) == NULL)
+ if (ret != 0 || (ifp = ifunit(name)) == NULL) {
+ LIST_REMOVE(ifcu, ifcu_list);
+ free(ifcu, M_TEMP, 0);
return (ret);
+ }
NET_LOCK();
if_addgroup(ifp, ifc->ifc_name);
@@ -1275,15 +1307,18 @@ if_clone_destroy(const char *name)
{
struct if_clone *ifc;
struct ifnet *ifp;
- int ret;
+ int unit, ret;
+
+ struct if_clone_unit *ifcu, *tifcu;
- ifc = if_clone_lookup(name, NULL);
+ ifc = if_clone_lookup(name, &unit);
if (ifc == NULL)
return (EINVAL);
ifp = ifunit(name);
if (ifp == NULL)
return (ENXIO);
+ ifp->if_dying = 1;
if (ifc->ifc_destroy == NULL)
return (EOPNOTSUPP);
@@ -1298,6 +1333,15 @@ if_clone_destroy(const char *name)
NET_UNLOCK();
ret = (*ifc->ifc_destroy)(ifp);
+ /* XXX: release unit */
+ LIST_FOREACH_SAFE(ifcu, &if_clone_units, ifcu_list, tifcu) {
+ if (ifcu->ifcu_ifc == ifc && ifcu->ifcu_unit == unit) {
+ LIST_REMOVE(ifcu, ifcu_list);
+ free(ifcu, M_TEMP, 0);
+ }
+ }
+ /* XXX: release unit */
+
return (ret);
}
@@ -1749,8 +1793,11 @@ ifunit(const char *name)
KERNEL_ASSERT_LOCKED();
TAILQ_FOREACH(ifp, &ifnet, if_list) {
- if (strcmp(ifp->if_xname, name) == 0)
+ if (strcmp(ifp->if_xname, name) == 0) {
+ if (ifp->if_dying)
+ ifp = NULL;
return (ifp);
+ }
}
return (NULL);
}
@@ -1958,6 +2005,7 @@ ifioctl(struct socket *so, u_long cmd, c
ifp = ifunit(ifr->ifr_name);
if (ifp == NULL)
return (ENXIO);
+ if_ref(ifp);
oif_flags = ifp->if_flags;
oif_xflags = ifp->if_xflags;
@@ -2314,6 +2362,7 @@ ifioctl(struct socket *so, u_long cmd, c
if (((oif_flags ^ ifp->if_flags) & IFF_UP) != 0)
getmicrotime(&ifp->if_lastchange);
+ if_put(ifp);
return (error);
}
@@ -2358,6 +2407,7 @@ ifioctl_get(u_long cmd, caddr_t data)
ifp = ifunit(ifr->ifr_name);
if (ifp == NULL)
return (ENXIO);
+ if_ref(ifp);
NET_RLOCK_IN_IOCTL();
@@ -2428,6 +2478,7 @@ ifioctl_get(u_long cmd, caddr_t data)
}
NET_RUNLOCK_IN_IOCTL();
+ if_put(ifp);
return (error);
}
@@ -2531,6 +2582,7 @@ ifconf(caddr_t data)
if (space == 0) {
TAILQ_FOREACH(ifp, &ifnet, if_list) {
struct sockaddr *sa;
+ if_ref(ifp);
if (TAILQ_EMPTY(&ifp->if_addrlist))
space += sizeof (ifr);
@@ -2543,6 +2595,7 @@ ifconf(caddr_t data)
sizeof(*sa);
space += sizeof(ifr);
}
+ if_put(ifp);
}
ifc->ifc_len = space;
return (0);
@@ -2550,6 +2603,7 @@ ifconf(caddr_t data)
ifrp = ifc->ifc_req;
TAILQ_FOREACH(ifp, &ifnet, if_list) {
+ if_ref(ifp);
if (space < sizeof(ifr))
break;
bcopy(ifp->if_xname, ifr.ifr_name, IFNAMSIZ);
@@ -2589,6 +2643,7 @@ ifconf(caddr_t data)
break;
space -= sizeof (ifr);
}
+ if_put(ifp);
}
ifc->ifc_len -= space;
return (error);
Index: sys/net/if_bridge.c
===================================================================
RCS file: /cvs/src/sys/net/if_bridge.c,v
retrieving revision 1.340
diff -u -p -r1.340 if_bridge.c
--- sys/net/if_bridge.c 24 Jun 2020 22:03:42 -0000 1.340
+++ sys/net/if_bridge.c 25 Jun 2020 09:30:46 -0000
@@ -234,7 +234,9 @@ bridge_clone_destroy(struct ifnet *ifp)
bstp_destroy(sc->sc_stp);
/* Undo pseudo-driver changes. */
+#if 0
if_deactivate(ifp);
+#endif
if_ih_remove(ifp, ether_input, NULL);
Index: sys/net/if_var.h
===================================================================
RCS file: /cvs/src/sys/net/if_var.h,v
retrieving revision 1.105
diff -u -p -r1.105 if_var.h
--- sys/net/if_var.h 12 May 2020 08:49:54 -0000 1.105
+++ sys/net/if_var.h 25 Jun 2020 09:30:46 -0000
@@ -185,6 +185,7 @@ struct ifnet { /* and the
entries */
struct sockaddr_dl *if_sadl; /* [N] pointer to our sockaddr_dl */
void *if_afdata[AF_MAX];
+ int if_dying;
};
#define if_mtu if_data.ifi_mtu
#define if_type if_data.ifi_type