[PATCH net] ppp: fix use-after-free reads in the stats ioctls.

Norbert Szetei <[email protected]> Sun, 28 Jun 2026 14:44:16 +0200
Newsgroups gmane.linux.ppp,gmane.linux.kernel,gmane.linux.network
Message-ID <[email protected]>
ppp_get_stats() (SIOCGPPPSTATS) and the SIOCGPPPCSTATS handler, both
reached from ppp_net_siocdevprivate(), dereference state that other
ioctls free under the ppp lock, without taking it:

  - ppp_get_stats() reads ppp->vj; PPPIOCSMAXCID frees it with
    slhc_free() under ppp_lock().
  - SIOCGPPPCSTATS calls ->comp_stat()/->decomp_stat() on
    ppp->xc_state / ppp->rc_state; PPPIOCSCOMPRESS and ppp_ccp_closed()
    free those.

A concurrent stats ioctl can then read freed memory (slab-use-after-
free), and the freed contents are copied back to userspace. This is=20
reachable by a local user who has CAP_NET_ADMIN privileges and=20
read/write access to /dev/ppp.

Take the lock the freeing path holds around each access: the receive
lock in ppp_get_stats() (PPPIOCSMAXCID frees ppp->vj under ppp_lock(),
which includes it) and ppp_lock() around the SIOCGPPPCSTATS callbacks.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Norbert Szetei <[email protected]>
---
 drivers/net/ppp/ppp_generic.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ppp/ppp_generic.c =
b/drivers/net/ppp/ppp_generic.c
index 57c68efa5ff8..847c5e1793c8 100644
--- a/drivers/net/ppp/ppp_generic.c
+++ b/drivers/net/ppp/ppp_generic.c
@@ -1505,10 +1505,13 @@ ppp_net_siocdevprivate(struct net_device *dev, =
struct ifreq *ifr,

 	case SIOCGPPPCSTATS:
 		memset(&cstats, 0, sizeof(cstats));
+		/* protect against PPPIOCSCOMPRESS/ppp_ccp_closed() =
freeing the state */
+		ppp_lock(ppp);
 		if (ppp->xc_state)
 			ppp->xcomp->comp_stat(ppp->xc_state, &cstats.c);
 		if (ppp->rc_state)
 			ppp->rcomp->decomp_stat(ppp->rc_state, =
&cstats.d);
+		ppp_unlock(ppp);
 		if (copy_to_user(addr, &cstats, sizeof(cstats)))
 			break;
 		err =3D 0;
@@ -3303,7 +3306,7 @@ find_compressor(int type)
 static void
 ppp_get_stats(struct ppp *ppp, struct ppp_stats *st)
 {
-	struct slcompress *vj =3D ppp->vj;
+	struct slcompress *vj;
 	int cpu;

 	memset(st, 0, sizeof(*st));
@@ -3323,8 +3326,14 @@ ppp_get_stats(struct ppp *ppp, struct ppp_stats =
*st)
 	}
 	st->p.ppp_ierrors =3D ppp->dev->stats.rx_errors;
 	st->p.ppp_oerrors =3D ppp->dev->stats.tx_errors;
-	if (!vj)
+
+	/* protect against PPPIOCSMAXCID freeing ppp->vj */
+	ppp_recv_lock(ppp);
+	vj =3D ppp->vj;
+	if (!vj) {
+		ppp_recv_unlock(ppp);
 		return;
+	}
 	st->vj.vjs_packets =3D vj->sls_o_compressed + =
vj->sls_o_uncompressed;
 	st->vj.vjs_compressed =3D vj->sls_o_compressed;
 	st->vj.vjs_searches =3D vj->sls_o_searches;
@@ -3333,6 +3342,7 @@ ppp_get_stats(struct ppp *ppp, struct ppp_stats =
*st)
 	st->vj.vjs_tossed =3D vj->sls_i_tossed;
 	st->vj.vjs_uncompressedin =3D vj->sls_i_uncompressed;
 	st->vj.vjs_compressedin =3D vj->sls_i_compressed;
+	ppp_recv_unlock(ppp);
 }

 /*
--
2.54.0=