[PATCH net 1/2] ovpn: don't deref NULL key slot in ovpn_crypto_kill_key()

Junrui Luo <[email protected]> Wed, 05 Aug 2026 21:29:36 +0800
Newsgroups org.kernel.feeds.b4-sent,org.kernel.vger.linux-kernel,org.kernel.vger.netdev,org.kernel.vger.stable
Message-ID <[email protected]>
ovpn_crypto_kill_key() is reached from ovpn_encrypt_post() when the
packet ID space of the key in use has been exhausted and
ovpn_pktid_xmit_next() returns -ERANGE. It locates the slot holding the
given key ID by reading cs->slots[0] and cs->slots[1] and comparing
->key_id, but it dereferences both pointers without first checking them
for NULL.

An empty key slot is a perfectly normal state. Both
ovpn_crypto_key_slot_delete() and ovpn_crypto_state_release() install
NULL, and the ordinary rekeying sequence - install a new key in the
secondary slot, swap, then delete the retired one - leaves primary_idx
at 1 with slots[0] empty. In that state the very first comparison
dereferences NULL. key_id sits at offset 0 of struct
ovpn_crypto_key_slot, so this faults on a read of address 0.

Every other slot accessor in this file already guards the pointer before
touching it, e.g. ovpn_crypto_key_id_to_slot():

	ks = rcu_dereference(cs->slots[idx]);
	if (ks && ks->key_id == key_id)

Use the same NULL-safe form here.

Fixes: 89d3c0e4612a ("ovpn: kill key and notify userspace in case of IV exhaustion")
Reported-by: Yuhao Jiang <[email protected]>
Assisted-by: Claude:claude-opus-5
Cc: [email protected]
Signed-off-by: Junrui Luo <[email protected]>
---
 drivers/net/ovpn/crypto.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c
index 90580e32052f..2c56fb180ed8 100644
--- a/drivers/net/ovpn/crypto.c
+++ b/drivers/net/ovpn/crypto.c
@@ -60,10 +60,12 @@ bool ovpn_crypto_kill_key(struct ovpn_crypto_state *cs, u8 key_id)
 	struct ovpn_crypto_key_slot *ks = NULL;
 
 	spin_lock_bh(&cs->lock);
-	if (rcu_access_pointer(cs->slots[0])->key_id == key_id) {
+	if (rcu_access_pointer(cs->slots[0]) &&
+	    rcu_access_pointer(cs->slots[0])->key_id == key_id) {
 		ks = rcu_replace_pointer(cs->slots[0], NULL,
 					 lockdep_is_held(&cs->lock));
-	} else if (rcu_access_pointer(cs->slots[1])->key_id == key_id) {
+	} else if (rcu_access_pointer(cs->slots[1]) &&
+		   rcu_access_pointer(cs->slots[1])->key_id == key_id) {
 		ks = rcu_replace_pointer(cs->slots[1], NULL,
 					 lockdep_is_held(&cs->lock));
 	}

-- 
2.51.2