diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml
index f3266763c325..d5713f5d1af7 100644
--- a/Documentation/netlink/specs/psp.yaml
+++ b/Documentation/netlink/specs/psp.yaml
@@ -265,7 +265,9 @@ operations:
-
name: rx-assoc
- doc: Allocate a new Rx key + SPI pair, associate it with a socket.
+ doc: |
+ Allocate a new Rx key + SPI pair, associate it with a socket.
+ If the socket already has PSP state, rekey its Rx direction.
attribute-set: assoc
do:
request:diff --git a/Documentation/networking/psp.rst b/Documentation/networking/psp.rst
index 0f9b6b73f244..fab797506b0a 100644
--- a/Documentation/networking/psp.rst
+++ b/Documentation/networking/psp.rst
@@ -140,14 +140,33 @@ The PSP assoc state of a socket is not reset when the connection is
torn down. ``connect()`` on a socket that has PSP assoc state will
return ``-EINVAL``.
+Rekeying
+--------
+
+A connection which has completed the exchange described above and is
+in the "PSP Full" state can be rekeyed in place without being torn
+down, using the same netlink calls as in connection setup.
+
+``rx-assoc`` allocates a new Rx key and SPI, which should be passed
+to the peer exactly as during the initial exchange. The SPI which was
+in use before the rekey remains acceptable on the socket until the
+next Rx rekey, or until the device key it was derived from has been
+rotated out, so packets the peer sent before it learned the new SPI
+are still received.
+
+A rekey is rejected with ``-EINVAL`` if it would change the PSP device
+or the PSP version of the association. It is rejected with ``-EBUSY``
+if the socket has not completed its initial exchange in both
+directions.
+
Rotation notifications
----------------------
The rotations of device key happen asynchronously and are usually
performed by management daemons, not under application control.
The PSP netlink family will generate a notification whenever keys
-are rotated. The applications are expected to re-establish connections
-before keys are rotated again.
+are rotated. Applications are expected to rekey their connections,
+as described above, before device keys are rotated again.
Kernel implementation
=====================
diff --git a/include/net/psp/functions.h b/include/net/psp/functions.h
index 410adb6718c1..5a7a518b9579 100644
--- a/include/net/psp/functions.h
+++ b/include/net/psp/functions.h
@@ -77,10 +77,12 @@ psp_is_allowed_nondata(struct sk_buff *skb, struct psp_assoc *pas)
static inline bool
psp_pse_matches_pas(struct psp_skb_ext *pse, struct psp_assoc *pas)
{
- return pse && pas->rx.spi == pse->spi &&
- pas->generation == pse->generation &&
- pas->version == pse->version &&
- pas->dev_id == pse->dev_id;
+ return pse && pas->version == pse->version &&
+ pas->dev_id == pse->dev_id &&
+ ((pas->rx.spi == pse->spi &&
+ pas->generation == pse->generation) ||
+ (pas->prev_spi && pas->prev_spi == pse->spi &&
+ pas->prev_generation == pse->generation));
}
static inline enum skb_drop_reasondiff --git a/include/net/psp/types.h b/include/net/psp/types.h
index b8905efbd604..9eb89d2d680c 100644
--- a/include/net/psp/types.h
+++ b/include/net/psp/types.h
@@ -3,6 +3,7 @@
#ifndef __NET_PSP_H
#define __NET_PSP_H
+#include <linux/bits.h>
#include <linux/mutex.h>
#include <linux/refcount.h>
#include <net/net_trackers.h>
@@ -78,7 +79,8 @@ struct psp_assoc_dev {
* @active_assocs: list of registered associations
* @prev_assocs: associations which use old (but still usable)
* device key
- * @stale_assocs: associations which use a rotated out key
+ * @stale_assocs: associations which use a rotated out key, or were
+ * superseded by a rekey
*
* @stats: statistics maintained by the core
* @stats.rotations: See stats attr key-rotations@@ -152,6 +154,15 @@ struct psp_key_parsed {
u8 key[PSP_MAX_KEY];
};
+/**
+ * enum psp_assoc_flags - flags of struct psp_assoc
+ * @PSP_ASSOC_SKIP_TX_KEY_DEL: Do not delete Tx key from psp_dev. It was
+ * copied to a newer psp_assoc during an Rx rekey.
+ */
+enum psp_assoc_flags {
+ PSP_ASSOC_SKIP_TX_KEY_DEL = BIT(0),
+};
+
struct psp_assoc {
struct psp_dev *psd;
@@ -159,6 +170,10 @@ struct psp_assoc {
u8 generation;
u8 version;
u8 peer_tx;
+ u8 prev_generation;
+ u8 flags; /* Slow path, protected by psd->lock */
+
+ __be32 prev_spi;
u32 upgrade_seq;
diff --git a/net/psp/psp.h b/net/psp/psp.h
index b123c2427905..92d7b92acaed 100644
--- a/net/psp/psp.h
+++ b/net/psp/psp.h
@@ -63,7 +63,8 @@ static inline bool psp_dev_has_sadb(struct psp_dev *psd)
static inline bool psp_assoc_needs_tx_key_del(struct psp_assoc *pas)
{
lockdep_assert_held(&pas->psd->lock);
- return psp_dev_has_sadb(pas->psd) && pas->tx.spi;
+ return psp_dev_has_sadb(pas->psd) && pas->tx.spi &&
+ !(pas->flags & PSP_ASSOC_SKIP_TX_KEY_DEL);
}
#endif /* __PSP_PSP_H */diff --git a/net/psp/psp_sock.c b/net/psp/psp_sock.c
index f2cba3fa3a23..c1e38845f08c 100644
--- a/net/psp/psp_sock.c
+++ b/net/psp/psp_sock.c
@@ -149,10 +149,39 @@ void psp_sk_assoc_free(struct sock *sk)
psp_assoc_put(pas);
}
+static int psp_sock_rx_rekey(struct psp_assoc *pas, struct psp_assoc *prev,
+ struct netlink_ext_ack *extack)
+{
+ if (pas->psd != prev->psd) {
+ NL_SET_ERR_MSG(extack, "PSP device mismatch with existing state");
+ return -EINVAL;
+ }
+ if (pas->version != prev->version) {
+ NL_SET_ERR_MSG(extack, "PSP version mismatch with existing state");
+ return -EINVAL;
+ }
+ if (!prev->tx.spi || !prev->peer_tx) {
+ NL_SET_ERR_MSG(extack, "Socket PSP state is not fully established");
+ return -EBUSY;
+ }
+
+ pas->peer_tx = 1;
+ pas->prev_spi = prev->rx.spi;
+ pas->prev_generation = prev->generation;
+
+ memcpy(&pas->tx, &prev->tx, sizeof(pas->tx));
+ memcpy(pas->drv_data, prev->drv_data, pas->psd->caps->assoc_drv_spc);
+ prev->flags |= PSP_ASSOC_SKIP_TX_KEY_DEL;
+ list_move(&prev->assocs_list, &prev->psd->stale_assocs);
+
+ return 0;
+}
+
int psp_sock_assoc_set_rx(struct sock *sk, struct psp_assoc *pas,
struct psp_key_parsed *key,
struct netlink_ext_ack *extack)
{
+ struct psp_assoc *prev;
int err;
memcpy(&pas->rx, key, sizeof(*key));@@ -165,10 +194,11 @@ int psp_sock_assoc_set_rx(struct sock *sk, struct psp_assoc *pas,
goto exit_unlock;
}
- if (psp_sk_assoc(sk)) {
- NL_SET_ERR_MSG(extack, "Socket already has PSP state");
- err = -EBUSY;
- goto exit_unlock;
+ prev = psp_sk_assoc(sk);
+ if (prev) {
+ err = psp_sock_rx_rekey(pas, prev, extack);
+ if (err)
+ goto exit_unlock;
} else if (sk_has_decrypt_user(sk)) {
NL_SET_ERR_MSG(extack, "Socket has incompatible state");
err = -EINVAL;@@ -177,6 +207,7 @@ int psp_sock_assoc_set_rx(struct sock *sk, struct psp_assoc *pas,
refcount_inc(&pas->refcnt);
rcu_assign_pointer(sk->psp_assoc, pas);
+ psp_assoc_put(prev);
err = 0;
exit_unlock:
@@ -302,8 +333,21 @@ void psp_assocs_key_rotated(struct psp_dev *psd)
*/
list_for_each_entry_safe(pas, next, &psd->prev_assocs, assocs_list) {
pas->generation |= ~PSP_GEN_VALID_MASK;
+ pas->prev_generation |= ~PSP_GEN_VALID_MASK;
psd->stats.stales++;
}
+
+ /* Don't allow SPI from two generations ago to alias one from the
+ * current generation. Rx rekeying under the same device key generation
+ * is currently permitted, so skip if the previous SPI is from just one
+ * generation ago.
+ */
+ list_for_each_entry(pas, &psd->active_assocs, assocs_list) {
+ if ((pas->rx.spi ^ pas->prev_spi) &
+ cpu_to_be32(PSP_SPI_KEY_PHASE))
+ pas->prev_generation |= ~PSP_GEN_VALID_MASK;
+ }
+
list_splice_init(&psd->prev_assocs, &psd->stale_assocs);
list_splice_init(&psd->active_assocs, &psd->prev_assocs);
--
2.52.0