summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/networking/xfrm/index.rst1
-rw-r--r--Documentation/networking/xfrm/xfrm_migrate_state.rst274
-rw-r--r--include/net/xfrm.h78
-rw-r--r--include/uapi/linux/xfrm.h25
-rw-r--r--net/ipv4/ipcomp.c2
-rw-r--r--net/ipv6/ipcomp6.c2
-rw-r--r--net/key/af_key.c12
-rw-r--r--net/xfrm/xfrm_compat.c5
-rw-r--r--net/xfrm/xfrm_device.c2
-rw-r--r--net/xfrm/xfrm_policy.c25
-rw-r--r--net/xfrm/xfrm_state.c144
-rw-r--r--net/xfrm/xfrm_user.c468
-rw-r--r--security/selinux/nlmsgtab.c3
13 files changed, 903 insertions, 138 deletions
diff --git a/Documentation/networking/xfrm/index.rst b/Documentation/networking/xfrm/index.rst
index 7d866da836fe..90191848f8db 100644
--- a/Documentation/networking/xfrm/index.rst
+++ b/Documentation/networking/xfrm/index.rst
@@ -9,5 +9,6 @@ XFRM Framework
xfrm_device
xfrm_proc
+ xfrm_migrate_state
xfrm_sync
xfrm_sysctl
diff --git a/Documentation/networking/xfrm/xfrm_migrate_state.rst b/Documentation/networking/xfrm/xfrm_migrate_state.rst
new file mode 100644
index 000000000000..9d53cb22b007
--- /dev/null
+++ b/Documentation/networking/xfrm/xfrm_migrate_state.rst
@@ -0,0 +1,274 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=====================
+XFRM SA Migrate State
+=====================
+
+Overview
+========
+
+``XFRM_MSG_MIGRATE_STATE`` migrates a single SA, looked up using SPI and
+mark, without involving policies. Unlike ``XFRM_MSG_MIGRATE``, which couples
+SA and policy migration and allows migrating multiple SAs in one call, this
+interface identifies the SA unambiguously via SPI and supports changing
+the reqid, addresses, encapsulation, selector, and offload.
+
+Because IKE daemons can manage policies independently of
+the kernel, this interface allows precise per-SA migration without
+requiring policy involvement. Optional netlink attributes follow an
+omit-to-inherit model: omitting an attribute preserves the value from
+the old SA. The ``flags`` field controls two exceptions: hardware offload
+is inherited by default and can be suppressed with
+``XFRM_MIGRATE_STATE_CLEAR_OFFLOAD`` or overridden with ``XFRMA_OFFLOAD_DEV``;
+the new selector is taken from ``new_sel`` by default and can instead be
+derived from the new addresses with ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL``.
+
+SA Identification
+=================
+
+The struct is defined in ``include/uapi/linux/xfrm.h``. The SA is looked
+up using ``xfrm_state_lookup()`` with ``id.spi``,
+``id.daddr``, ``id.proto``, ``id.family``, and
+``old_mark.v & old_mark.m`` as the mark key::
+
+ struct xfrm_user_migrate_state {
+ struct xfrm_usersa_id id; /* spi, daddr, proto, family */
+ xfrm_address_t new_daddr;
+ xfrm_address_t new_saddr;
+ struct xfrm_mark old_mark; /* SA lookup: key = v & m */
+ struct xfrm_selector new_sel; /* new selector (see Flags) */
+ __u32 new_reqid;
+ __u32 flags; /* XFRM_MIGRATE_STATE_* */
+ __u16 new_family;
+ __u16 reserved; /* must be zero */
+ };
+
+The ``reserved`` field must be set to zero; the kernel rejects any
+other value with ``-EINVAL``.
+
+Supported Attributes
+====================
+
+The following fields in ``xfrm_user_migrate_state`` are always explicit
+and are not inherited from the existing SA. Passing zero is not equivalent
+to "keep unchanged" — zero is used as-is:
+
+- ``new_daddr`` - new destination address
+- ``new_saddr`` - new source address
+- ``new_family`` - new address family
+- ``new_reqid`` - new reqid (0 = no reqid)
+- ``new_sel`` - new selector; used when ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL`` is
+ not set (see `Flags`_ below)
+- ``flags`` - bitmask of ``XFRM_MIGRATE_STATE_*`` flags (see `Flags`_ below)
+
+The following netlink attributes are also accepted. Omitting an attribute
+inherits the value from the existing SA (omit-to-inherit).
+
+.. list-table::
+ :widths: 30 70
+ :header-rows: 1
+
+ * - Attribute
+ - Description
+ * - ``XFRMA_MARK``
+ - Mark on the migrated SA (``struct xfrm_mark``). Absent inherits
+ ``old_mark``. To use no mark on the new SA, send ``XFRMA_MARK``
+ with ``{0, 0}``.
+ * - ``XFRMA_ENCAP``
+ - UDP encapsulation template; only ``UDP_ENCAP_ESPINUDP`` is supported.
+ Set ``encap_type=0`` to remove encap.
+ * - ``XFRMA_OFFLOAD_DEV``
+ - Hardware offload configuration (``struct xfrm_user_offload``). Absent
+ copies offload from the existing SA. When
+ ``XFRM_MIGRATE_STATE_CLEAR_OFFLOAD`` is set in ``flags``, the new SA has
+ no offload; this flag is mutually exclusive with ``XFRMA_OFFLOAD_DEV``
+ and sending both returns ``-EINVAL``.
+ * - ``XFRMA_SET_MARK``
+ - Output mark on the migrated SA; pair with ``XFRMA_SET_MARK_MASK``.
+ Send 0 to clear.
+ * - ``XFRMA_NAT_KEEPALIVE_INTERVAL``
+ - NAT keepalive interval in seconds. Requires encap. Send 0 to clear.
+ Automatically cleared when encap is removed; setting a non-zero
+ value without encap returns ``-EINVAL``.
+ * - ``XFRMA_MTIMER_THRESH``
+ - Mapping maxage threshold. Only valid on input SAs; setting on an
+ output SA returns ``-EINVAL``. Requires encap. Send 0 to clear.
+ Automatically cleared when encap is removed; setting a non-zero
+ value without encap returns ``-EINVAL``.
+
+The following SA properties are immutable and cannot be changed via
+``XFRM_MSG_MIGRATE_STATE``: algorithms (``XFRMA_ALG_*``), replay state,
+direction (``XFRMA_SA_DIR``), and security context (``XFRMA_SEC_CTX``).
+
+Flags
+=====
+
+The ``flags`` field in ``xfrm_user_migrate_state`` controls optional
+migration behaviour. Unknown flag bits are rejected with ``-EINVAL``; the
+extended ACK message identifies the unrecognised bits (e.g. ``"Unknown flags:
+0x4"``). Userspace can use ``XFRM_MIGRATE_STATE_KNOWN_FLAGS`` (defined in
+``<linux/xfrm.h>``) to validate flags before sending; note that this constant
+reflects the flags known to the header version userspace was compiled against,
+which may differ from what the running kernel accepts.
+
+.. list-table::
+ :widths: 40 60
+ :header-rows: 1
+
+ * - Flag
+ - Description
+ * - ``XFRM_MIGRATE_STATE_CLEAR_OFFLOAD``
+ - When set, the new SA has no hardware offload even when
+ ``XFRMA_OFFLOAD_DEV`` is absent. Without this flag, omitting
+ ``XFRMA_OFFLOAD_DEV`` copies the existing offload to the new SA.
+ Mutually exclusive with ``XFRMA_OFFLOAD_DEV``; sending both
+ returns ``-EINVAL``.
+ * - ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL``
+ - When set, the kernel validates that the existing SA selector is a
+ single-host entry matching the SA addresses (``prefixlen_s ==
+ prefixlen_d`` equal to 32 for IPv4 or 128 for IPv6, and addresses
+ matching ``id.daddr`` and ``props.saddr``). If the check passes,
+ the new selector is derived from ``new_daddr`` and ``new_saddr``
+ with the single-host mask for ``new_family``. A mismatch returns
+ ``-EINVAL``. When this flag is not set, ``new_sel`` is used as-is
+ for the migrated SA.
+
+Migration Steps
+===============
+
+Outgoing SA
+-----------
+
+To prevent cleartext traffic leaks, install a block policy before
+migrating:
+
+#. Install a block policy to drop traffic on the affected selector.
+#. Remove the old policy.
+#. Call ``XFRM_MSG_MIGRATE_STATE`` for each SA.
+#. Reinstall the policies.
+#. Remove the block policy.
+
+If AES-GCM is in use, the block policy also prevents IV reuse during
+the migration window. For other AEADs this step is not required for
+IV safety, but skipping it allows a brief cleartext window.
+
+Incoming SA
+-----------
+
+No block policy is needed. ``XFRM_MSG_MIGRATE_STATE`` atomically
+transfers the sequence number and replay window from the old SA to
+the new SA, so the new SA continues replay protection without a gap.
+Call ``XFRM_MSG_MIGRATE_STATE`` for each SA directly.
+
+When accepting incoming traffic, be liberal during the migration
+window: packets sent by the remote peer before it completed its own
+migration may arrive out of order or slightly late. Dropping them
+unnecessarily causes packet loss. A generous replay window reduces
+the impact of reordering during migration.
+
+Block Policy and IV Safety
+--------------------------
+
+AES-GCM IV uniqueness is critical: reusing a (key, IV) pair allows
+an attacker to recover the authentication subkey and forge
+authentication tags, breaking both confidentiality and integrity.
+This concern applies to outgoing SAs only — the remote peer controls
+IV generation on incoming traffic.
+
+``XFRM_MSG_MIGRATE_STATE`` atomically copies the sequence number and
+replay window from the old SA to the new SA and deletes the old SA.
+The block policy serves two purposes: it prevents cleartext traffic
+leaks during the migration window, and for AES-GCM it prevents IV
+reuse by ensuring no outgoing packets are sent under the same key.
+The atomic copy of the sequence number and replay window complements
+this — together they eliminate both risks during migration.
+The atomic copy also serves incoming SAs, ensuring replay protection
+continues without a gap across the migration.
+
+Feature Detection
+=================
+
+Userspace can probe for kernel support by sending a minimal
+``XFRM_MSG_MIGRATE_STATE`` message with a non-zero non-existent SPI:
+
+- ``-EINVAL``: kernel predates ``XFRM_MSG_MIGRATE_STATE``; message type
+ is out of range
+- ``-ENOPROTOOPT``: message type is known but ``CONFIG_XFRM_MIGRATE``
+ is not enabled
+- ``-ESRCH``: supported (SPI not found)
+
+Userspace Notification on Success
+=================================
+
+On successful migration the kernel multicasts an
+``XFRM_MSG_MIGRATE_STATE`` message to the ``XFRMNLGRP_MIGRATE`` group.
+The fixed header is ``struct xfrm_user_migrate_state`` copied from the
+request, followed by the same set of netlink attributes that are
+accepted as input, with the differences noted below.
+
+Differences from the request
+-----------------------------
+
+.. list-table::
+ :widths: 25 75
+ :header-rows: 1
+
+ * - Field / Attribute
+ - Difference
+ * - ``new_sel``
+ - Contains the actual selector of the newly installed SA, not the
+ ``new_sel`` from the request. When
+ ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL`` is set the kernel derives the
+ selector from ``new_daddr`` / ``new_saddr``; the caller's
+ ``new_sel`` field is ignored in that case. The notification
+ always carries the real selector of the new SA.
+ * - ``XFRMA_SA_DIR``
+ - Present in the notification (set from the direction of the new
+ SA) but **not accepted as input** — direction is immutable.
+ * - ``flags``
+ - Echoed back as-is. ``XFRM_MIGRATE_STATE_CLEAR_OFFLOAD`` and
+ ``XFRM_MIGRATE_STATE_UPDATE_H2H_SEL`` describe the request that was
+ made, not a property of the resulting SA.
+
+Attributes in the notification
+-------------------------------
+
+.. list-table::
+ :widths: 30 70
+ :header-rows: 1
+
+ * - Attribute
+ - Description
+ * - ``XFRMA_ENCAP``
+ - UDP encapsulation template, if configured on the new SA.
+ * - ``XFRMA_OFFLOAD_DEV``
+ - Hardware offload configuration, if active on the new SA.
+ * - ``XFRMA_MARK``
+ - Mark on the new SA, if set.
+ * - ``XFRMA_SET_MARK``
+ - Output mark on the new SA, if set.
+ * - ``XFRMA_SET_MARK_MASK``
+ - Output mark mask, present together with ``XFRMA_SET_MARK``.
+ * - ``XFRMA_MTIMER_THRESH``
+ - Mapping maxage threshold, if non-zero.
+ * - ``XFRMA_NAT_KEEPALIVE_INTERVAL``
+ - NAT keepalive interval, if non-zero.
+ * - ``XFRMA_SA_DIR``
+ - Direction of the new SA.
+
+Error Handling
+==============
+
+If the target SA tuple (new daddr, SPI, proto, new family) is already
+occupied, the operation returns ``-EEXIST`` before the migration begins.
+The old SA remains intact and the operation is safe to retry after
+resolving the conflict.
+
+If the target SA is deleted before the migration completes, the operation
+returns ``-ESRCH``. No new SA is installed. Userspace should verify the
+current SA state before retrying.
+
+If the multicast notification (``XFRMNLGRP_MIGRATE``) fails to send,
+the migration itself has already completed successfully and the new SA
+is installed. The operation returns success, 0, with an extack warning,
+but listeners will not receive the migration event.
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 10d3edde6b2f..e2cb2d0e5cee 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -682,12 +682,22 @@ struct xfrm_migrate {
xfrm_address_t old_saddr;
xfrm_address_t new_daddr;
xfrm_address_t new_saddr;
+ struct xfrm_encap_tmpl *encap;
+ struct xfrm_user_offload *xuo;
+ struct xfrm_mark old_mark;
+ const struct xfrm_mark *new_mark;
+ struct xfrm_mark smark;
u8 proto;
u8 mode;
- u16 reserved;
- u32 reqid;
+ u16 msg_type; /* XFRM_MSG_MIGRATE or XFRM_MSG_MIGRATE_STATE */
+ u32 flags;
+ u32 old_reqid;
+ u32 new_reqid;
+ u32 nat_keepalive_interval;
+ u32 mapping_maxage;
u16 old_family;
u16 new_family;
+ const struct xfrm_selector *new_sel;
};
#define XFRM_KM_TIMEOUT 30
@@ -1774,7 +1784,7 @@ u32 xfrm_replay_seqhi(struct xfrm_state *x, __be32 net_seq);
int xfrm_init_replay(struct xfrm_state *x, struct netlink_ext_ack *extack);
u32 xfrm_state_mtu(struct xfrm_state *x, int mtu);
int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack);
-int xfrm_init_state(struct xfrm_state *x);
+int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack);
int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type);
int xfrm_input_resume(struct sk_buff *skb, int nexthdr);
int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb,
@@ -1895,11 +1905,17 @@ int km_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
const struct xfrm_encap_tmpl *encap);
struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *net,
u32 if_id);
+struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
+ const struct xfrm_migrate *m,
+ struct net *net,
+ struct netlink_ext_ack *extack);
+int xfrm_state_migrate_install(const struct xfrm_state *x,
+ struct xfrm_state *xc,
+ const struct xfrm_migrate *m,
+ struct netlink_ext_ack *extack);
struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
struct xfrm_migrate *m,
- struct xfrm_encap_tmpl *encap,
struct net *net,
- struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack);
int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
struct xfrm_migrate *m, int num_bundles,
@@ -2013,23 +2029,51 @@ static inline unsigned int xfrm_replay_state_esn_len(struct xfrm_replay_state_es
#ifdef CONFIG_XFRM_MIGRATE
static inline int xfrm_replay_clone(struct xfrm_state *x,
- struct xfrm_state *orig)
+ const struct xfrm_state *orig)
{
+ /* Counters synced later in xfrm_replay_sync() */
+
+ x->replay = orig->replay;
+ x->preplay = orig->preplay;
- x->replay_esn = kmemdup(orig->replay_esn,
+ if (orig->replay_esn) {
+ x->replay_esn = kmemdup(orig->replay_esn,
xfrm_replay_state_esn_len(orig->replay_esn),
GFP_KERNEL);
- if (!x->replay_esn)
- return -ENOMEM;
- x->preplay_esn = kmemdup(orig->preplay_esn,
- xfrm_replay_state_esn_len(orig->preplay_esn),
- GFP_KERNEL);
- if (!x->preplay_esn)
- return -ENOMEM;
+ if (!x->replay_esn)
+ return -ENOMEM;
+ x->preplay_esn = kmemdup(orig->preplay_esn,
+ xfrm_replay_state_esn_len(orig->preplay_esn),
+ GFP_KERNEL);
+ if (!x->preplay_esn)
+ return -ENOMEM;
+ }
return 0;
}
+static inline void xfrm_replay_sync(struct xfrm_state *x, const struct xfrm_state *orig)
+{
+ x->replay = orig->replay;
+ x->preplay = orig->preplay;
+
+ if (orig->replay_esn) {
+ memcpy(x->replay_esn, orig->replay_esn,
+ xfrm_replay_state_esn_len(orig->replay_esn));
+
+ memcpy(x->preplay_esn, orig->preplay_esn,
+ xfrm_replay_state_esn_len(orig->preplay_esn));
+ }
+}
+
+static inline void xfrm_migrate_sync(struct xfrm_state *x,
+ const struct xfrm_state *orig)
+{
+ /* called under lock so no race conditions or mallocs allowed */
+ memcpy(&x->curlft, &orig->curlft, sizeof(x->curlft));
+ xfrm_replay_sync(x, orig);
+}
+
static inline struct xfrm_algo_aead *xfrm_algo_aead_clone(struct xfrm_algo_aead *orig)
{
return kmemdup(orig, aead_len(orig), GFP_KERNEL);
@@ -2068,7 +2112,7 @@ void xfrm_dev_resume(struct sk_buff *skb);
void xfrm_dev_backlog(struct softnet_data *sd);
struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t features, bool *again);
int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
- struct xfrm_user_offload *xuo,
+ const struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack);
int xfrm_dev_policy_add(struct net *net, struct xfrm_policy *xp,
struct xfrm_user_offload *xuo, u8 dir,
@@ -2139,7 +2183,9 @@ static inline struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_fea
return skb;
}
-static inline int xfrm_dev_state_add(struct net *net, struct xfrm_state *x, struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack)
+static inline int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
+ const struct xfrm_user_offload *xuo,
+ struct netlink_ext_ack *extack)
{
return 0;
}
diff --git a/include/uapi/linux/xfrm.h b/include/uapi/linux/xfrm.h
index a23495c0e0a1..051f8066efd1 100644
--- a/include/uapi/linux/xfrm.h
+++ b/include/uapi/linux/xfrm.h
@@ -227,6 +227,9 @@ enum {
#define XFRM_MSG_SETDEFAULT XFRM_MSG_SETDEFAULT
XFRM_MSG_GETDEFAULT,
#define XFRM_MSG_GETDEFAULT XFRM_MSG_GETDEFAULT
+
+ XFRM_MSG_MIGRATE_STATE,
+#define XFRM_MSG_MIGRATE_STATE XFRM_MSG_MIGRATE_STATE
__XFRM_MSG_MAX
};
#define XFRM_MSG_MAX (__XFRM_MSG_MAX - 1)
@@ -507,6 +510,28 @@ struct xfrm_user_migrate {
__u16 new_family;
};
+struct xfrm_user_migrate_state {
+ struct xfrm_usersa_id id;
+ xfrm_address_t new_daddr;
+ xfrm_address_t new_saddr;
+ struct xfrm_mark old_mark;
+ struct xfrm_selector new_sel;
+ __u32 new_reqid;
+ __u32 flags;
+ __u16 new_family;
+ __u16 reserved;
+};
+
+/* Flags for xfrm_user_migrate_state.flags */
+enum xfrm_migrate_state_flags {
+ XFRM_MIGRATE_STATE_CLEAR_OFFLOAD = 1, /* do not inherit offload from existing SA */
+ XFRM_MIGRATE_STATE_UPDATE_H2H_SEL = 2, /* update H2H selector from new daddr/saddr */
+};
+
+/* All flags defined as of this header version; unknown bits are rejected. */
+#define XFRM_MIGRATE_STATE_KNOWN_FLAGS \
+ (XFRM_MIGRATE_STATE_CLEAR_OFFLOAD | XFRM_MIGRATE_STATE_UPDATE_H2H_SEL)
+
struct xfrm_user_mapping {
struct xfrm_usersa_id id;
__u32 reqid;
diff --git a/net/ipv4/ipcomp.c b/net/ipv4/ipcomp.c
index 9a45aed508d1..b1ea2d37e8c5 100644
--- a/net/ipv4/ipcomp.c
+++ b/net/ipv4/ipcomp.c
@@ -77,7 +77,7 @@ static struct xfrm_state *ipcomp_tunnel_create(struct xfrm_state *x)
memcpy(&t->mark, &x->mark, sizeof(t->mark));
t->if_id = x->if_id;
- if (xfrm_init_state(t))
+ if (xfrm_init_state(t, NULL))
goto error;
atomic_set(&t->tunnel_users, 1);
diff --git a/net/ipv6/ipcomp6.c b/net/ipv6/ipcomp6.c
index 8607569de34f..b340d67eb1d9 100644
--- a/net/ipv6/ipcomp6.c
+++ b/net/ipv6/ipcomp6.c
@@ -95,7 +95,7 @@ static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x)
memcpy(&t->mark, &x->mark, sizeof(t->mark));
t->if_id = x->if_id;
- if (xfrm_init_state(t))
+ if (xfrm_init_state(t, NULL))
goto error;
atomic_set(&t->tunnel_users, 1);
diff --git a/net/key/af_key.c b/net/key/af_key.c
index a166a88d8788..1f0201d97b4f 100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -1299,7 +1299,7 @@ static struct xfrm_state * pfkey_msg2xfrm_state(struct net *net,
}
}
- err = xfrm_init_state(x);
+ err = xfrm_init_state(x, NULL);
if (err)
goto out;
@@ -2554,7 +2554,7 @@ static int ipsecrequests_to_migrate(struct sadb_x_ipsecrequest *rq1, int len,
if ((mode = pfkey_mode_to_xfrm(rq1->sadb_x_ipsecrequest_mode)) < 0)
return -EINVAL;
m->mode = mode;
- m->reqid = rq1->sadb_x_ipsecrequest_reqid;
+ m->old_reqid = rq1->sadb_x_ipsecrequest_reqid;
return ((int)(rq1->sadb_x_ipsecrequest_len +
rq2->sadb_x_ipsecrequest_len));
@@ -3655,15 +3655,15 @@ static int pfkey_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
if (mode < 0)
goto err;
if (set_ipsecrequest(skb, mp->proto, mode,
- (mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
- mp->reqid, mp->old_family,
+ (mp->old_reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
+ mp->old_reqid, mp->old_family,
&mp->old_saddr, &mp->old_daddr) < 0)
goto err;
/* new ipsecrequest */
if (set_ipsecrequest(skb, mp->proto, mode,
- (mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
- mp->reqid, mp->new_family,
+ (mp->old_reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
+ mp->old_reqid, mp->new_family,
&mp->new_saddr, &mp->new_daddr) < 0)
goto err;
}
diff --git a/net/xfrm/xfrm_compat.c b/net/xfrm/xfrm_compat.c
index b8d2e6930041..670e3512fc09 100644
--- a/net/xfrm/xfrm_compat.c
+++ b/net/xfrm/xfrm_compat.c
@@ -94,7 +94,8 @@ static const int compat_msg_min[XFRM_NR_MSGTYPES] = {
[XFRM_MSG_GETSADINFO - XFRM_MSG_BASE] = sizeof(u32),
[XFRM_MSG_NEWSPDINFO - XFRM_MSG_BASE] = sizeof(u32),
[XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = sizeof(u32),
- [XFRM_MSG_MAPPING - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_mapping)
+ [XFRM_MSG_MAPPING - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_mapping),
+ [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_migrate_state),
};
static const struct nla_policy compat_policy[XFRMA_MAX+1] = {
@@ -162,6 +163,7 @@ static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb,
case XFRM_MSG_NEWAE:
case XFRM_MSG_REPORT:
case XFRM_MSG_MIGRATE:
+ case XFRM_MSG_MIGRATE_STATE:
case XFRM_MSG_NEWSADINFO:
case XFRM_MSG_NEWSPDINFO:
case XFRM_MSG_MAPPING:
@@ -498,6 +500,7 @@ static int xfrm_xlate32(struct nlmsghdr *dst, const struct nlmsghdr *src,
case XFRM_MSG_GETAE:
case XFRM_MSG_REPORT:
case XFRM_MSG_MIGRATE:
+ case XFRM_MSG_MIGRATE_STATE:
case XFRM_MSG_NEWSADINFO:
case XFRM_MSG_GETSADINFO:
case XFRM_MSG_NEWSPDINFO:
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 550457e4c4f0..630f3dd31cc5 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -229,7 +229,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
- struct xfrm_user_offload *xuo,
+ const struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack)
{
int err;
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index c944327ce66c..1c558362d375 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4538,7 +4538,7 @@ static int migrate_tmpl_match(const struct xfrm_migrate *m, const struct xfrm_tm
int match = 0;
if (t->mode == m->mode && t->id.proto == m->proto &&
- (m->reqid == 0 || t->reqid == m->reqid)) {
+ (m->old_reqid == 0 || t->reqid == m->old_reqid)) {
switch (t->mode) {
case XFRM_MODE_TUNNEL:
case XFRM_MODE_BEET:
@@ -4632,7 +4632,7 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate,
sizeof(m[i].old_saddr)) &&
m[i].proto == m[j].proto &&
m[i].mode == m[j].mode &&
- m[i].reqid == m[j].reqid &&
+ m[i].old_reqid == m[j].old_reqid &&
m[i].old_family == m[j].old_family) {
NL_SET_ERR_MSG(extack, "Entries in the MIGRATE attribute's list must be unique");
return -EINVAL;
@@ -4643,6 +4643,21 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate,
return 0;
}
+/*
+ * Fill migrate fields that are invariant in XFRM_MSG_MIGRATE: inherited
+ * from the existing SA unchanged. XFRM_MSG_MIGRATE_STATE can update these.
+ */
+static void xfrm_migrate_copy_old(const struct xfrm_state *x,
+ struct xfrm_migrate *mp)
+{
+ mp->msg_type = XFRM_MSG_MIGRATE;
+ mp->smark = x->props.smark;
+ mp->new_reqid = x->props.reqid;
+ mp->nat_keepalive_interval = x->nat_keepalive_interval;
+ mp->mapping_maxage = x->mapping_maxage;
+ mp->new_mark = &x->mark;
+}
+
int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
struct xfrm_migrate *m, int num_migrate,
struct xfrm_kmaddress *k, struct net *net,
@@ -4680,7 +4695,11 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
if ((x = xfrm_migrate_state_find(mp, net, if_id))) {
x_cur[nx_cur] = x;
nx_cur++;
- xc = xfrm_state_migrate(x, mp, encap, net, xuo, extack);
+ mp->encap = encap;
+ mp->xuo = xuo;
+ xfrm_migrate_copy_old(x, mp);
+
+ xc = xfrm_state_migrate(x, mp, net, extack);
if (xc) {
x_new[nx_new] = xc;
nx_new++;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 1748d374abca..2c738e980b3f 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1966,8 +1966,7 @@ static inline int clone_security(struct xfrm_state *x, struct xfrm_sec_ctx *secu
}
static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
- struct xfrm_encap_tmpl *encap,
- struct xfrm_migrate *m)
+ const struct xfrm_migrate *m)
{
struct net *net = xs_net(orig);
struct xfrm_state *x = xfrm_state_alloc(net);
@@ -1975,13 +1974,25 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
goto out;
memcpy(&x->id, &orig->id, sizeof(x->id));
- memcpy(&x->sel, &orig->sel, sizeof(x->sel));
+ if (m->msg_type == XFRM_MSG_MIGRATE_STATE) {
+ if (m->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL) {
+ u8 prefixlen = (m->new_family == AF_INET6) ? 128 : 32;
+
+ x->sel = orig->sel;
+ x->sel.family = m->new_family;
+ x->sel.prefixlen_d = prefixlen;
+ x->sel.prefixlen_s = prefixlen;
+ x->sel.daddr = m->new_daddr;
+ x->sel.saddr = m->new_saddr;
+ } else {
+ x->sel = *m->new_sel;
+ }
+ } else {
+ x->sel = orig->sel;
+ }
memcpy(&x->lft, &orig->lft, sizeof(x->lft));
x->props.mode = orig->props.mode;
x->props.replay_window = orig->props.replay_window;
- x->props.reqid = orig->props.reqid;
- x->props.family = orig->props.family;
- x->props.saddr = orig->props.saddr;
if (orig->aalg) {
x->aalg = xfrm_algo_auth_clone(orig->aalg);
@@ -2010,16 +2021,12 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
}
x->props.calgo = orig->props.calgo;
- if (encap || orig->encap) {
- if (encap)
- x->encap = kmemdup(encap, sizeof(*x->encap),
- GFP_KERNEL);
- else
- x->encap = kmemdup(orig->encap, sizeof(*x->encap),
- GFP_KERNEL);
-
+ if (m->encap) {
+ x->encap = kmemdup(m->encap, sizeof(*x->encap), GFP_KERNEL);
if (!x->encap)
goto error;
+ x->mapping_maxage = m->mapping_maxage;
+ x->nat_keepalive_interval = m->nat_keepalive_interval;
}
if (orig->security)
@@ -2033,13 +2040,12 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
goto error;
}
- if (orig->replay_esn) {
- if (xfrm_replay_clone(x, orig))
- goto error;
- }
+ if (xfrm_replay_clone(x, orig))
+ goto error;
- memcpy(&x->mark, &orig->mark, sizeof(x->mark));
- memcpy(&x->props.smark, &orig->props.smark, sizeof(x->props.smark));
+ x->mark = m->new_mark ? *m->new_mark : m->old_mark;
+
+ x->props.smark = m->smark;
x->props.flags = orig->props.flags;
x->props.extra_flags = orig->props.extra_flags;
@@ -2049,12 +2055,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
x->tfcpad = orig->tfcpad;
x->replay_maxdiff = orig->replay_maxdiff;
x->replay_maxage = orig->replay_maxage;
- memcpy(&x->curlft, &orig->curlft, sizeof(x->curlft));
x->km.state = orig->km.state;
x->km.seq = orig->km.seq;
- x->replay = orig->replay;
- x->preplay = orig->preplay;
- x->mapping_maxage = orig->mapping_maxage;
x->lastused = orig->lastused;
x->new_mapping = 0;
x->new_mapping_sport = 0;
@@ -2066,7 +2068,7 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
goto error;
}
-
+ x->props.reqid = m->new_reqid;
x->props.family = m->new_family;
memcpy(&x->id.daddr, &m->new_daddr, sizeof(x->id.daddr));
memcpy(&x->props.saddr, &m->new_saddr, sizeof(x->props.saddr));
@@ -2088,14 +2090,14 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
spin_lock_bh(&net->xfrm.xfrm_state_lock);
- if (m->reqid) {
+ if (m->old_reqid) {
h = xfrm_dst_hash(net, &m->old_daddr, &m->old_saddr,
- m->reqid, m->old_family);
+ m->old_reqid, m->old_family);
hlist_for_each_entry(x, xfrm_state_deref_prot(net->xfrm.state_bydst, net) + h, bydst) {
if (x->props.mode != m->mode ||
x->id.proto != m->proto)
continue;
- if (m->reqid && x->props.reqid != m->reqid)
+ if (m->old_reqid && x->props.reqid != m->old_reqid)
continue;
if (if_id != 0 && x->if_id != if_id)
continue;
@@ -2132,45 +2134,81 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
}
EXPORT_SYMBOL(xfrm_migrate_state_find);
-struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
- struct xfrm_migrate *m,
- struct xfrm_encap_tmpl *encap,
- struct net *net,
- struct xfrm_user_offload *xuo,
- struct netlink_ext_ack *extack)
+struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
+ const struct xfrm_migrate *m,
+ struct net *net,
+ struct netlink_ext_ack *extack)
{
struct xfrm_state *xc;
- xc = xfrm_state_clone_and_setup(x, encap, m);
- if (!xc)
+ xc = xfrm_state_clone_and_setup(x, m);
+ if (!xc) {
+ NL_SET_ERR_MSG(extack, "Failed to clone and setup state");
return NULL;
+ }
- if (xfrm_init_state(xc) < 0)
+ if (xfrm_init_state(xc, extack) < 0) {
+ NL_SET_ERR_MSG_WEAK(extack, "Failed to initialize migrated state");
goto error;
+ }
/* configure the hardware if offload is requested */
- if (xuo && xfrm_dev_state_add(net, xc, xuo, extack))
+ if (m->xuo && xfrm_dev_state_add(net, xc, m->xuo, extack))
goto error;
- /* add state */
- if (xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
- /* a care is needed when the destination address of the
- state is to be updated as it is a part of triplet */
- xfrm_state_insert(xc);
- } else {
- if (xfrm_state_add(xc) < 0)
- goto error_add;
- }
-
return xc;
-error_add:
- if (xuo)
- xfrm_dev_state_delete(xc);
error:
xc->km.state = XFRM_STATE_DEAD;
xfrm_state_put(xc);
return NULL;
}
+EXPORT_SYMBOL(xfrm_state_migrate_create);
+
+int xfrm_state_migrate_install(const struct xfrm_state *x,
+ struct xfrm_state *xc,
+ const struct xfrm_migrate *m,
+ struct netlink_ext_ack *extack)
+{
+ if (m->new_family == m->old_family &&
+ xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
+ /*
+ * Care is needed when the destination address of the state is
+ * to be updated as it is a part of triplet.
+ */
+ xfrm_state_insert(xc);
+ } else {
+ if (xfrm_state_add(xc) < 0) {
+ NL_SET_ERR_MSG(extack, "Failed to add migrated state");
+ if (m->xuo)
+ xfrm_dev_state_delete(xc);
+ xc->km.state = XFRM_STATE_DEAD;
+ xfrm_state_put(xc);
+ return -EEXIST;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(xfrm_state_migrate_install);
+
+struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
+ struct xfrm_migrate *m,
+ struct net *net,
+ struct netlink_ext_ack *extack)
+{
+ struct xfrm_state *xc;
+
+ xc = xfrm_state_migrate_create(x, m, net, extack);
+ if (!xc)
+ return NULL;
+
+ xfrm_migrate_sync(xc, x);
+
+ if (xfrm_state_migrate_install(x, xc, m, extack) < 0)
+ return NULL;
+
+ return xc;
+}
EXPORT_SYMBOL(xfrm_state_migrate);
#endif
@@ -3240,11 +3278,11 @@ error:
EXPORT_SYMBOL(__xfrm_init_state);
-int xfrm_init_state(struct xfrm_state *x)
+int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
{
int err;
- err = __xfrm_init_state(x, NULL);
+ err = __xfrm_init_state(x, extack);
if (err)
return err;
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index e87f33aaa99c..e1010b5315e9 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -248,66 +248,90 @@ static inline int verify_replay(struct xfrm_usersa_info *p,
return 0;
}
-static int verify_newsa_info(struct xfrm_usersa_info *p,
- struct nlattr **attrs,
- struct netlink_ext_ack *extack)
+static int verify_mtimer_thresh(bool has_encap, u8 dir,
+ struct netlink_ext_ack *extack)
{
- int err;
- u8 sa_dir = nla_get_u8_default(attrs[XFRMA_SA_DIR], 0);
- u16 family = p->sel.family;
+ if (!has_encap) {
+ NL_SET_ERR_MSG(extack,
+ "MTIMER_THRESH requires encapsulation");
+ return -EINVAL;
+ }
+ if (dir == XFRM_SA_DIR_OUT) {
+ NL_SET_ERR_MSG(extack,
+ "MTIMER_THRESH should not be set on output SA");
+ return -EINVAL;
+ }
+ return 0;
+}
- err = -EINVAL;
- switch (p->family) {
+static int verify_xfrm_family(u16 family, struct netlink_ext_ack *extack)
+{
+ switch (family) {
case AF_INET:
- break;
-
+ return 0;
case AF_INET6:
#if IS_ENABLED(CONFIG_IPV6)
- break;
+ return 0;
#else
- err = -EAFNOSUPPORT;
NL_SET_ERR_MSG(extack, "IPv6 support disabled");
- goto out;
+ return -EAFNOSUPPORT;
#endif
-
default:
NL_SET_ERR_MSG(extack, "Invalid address family");
- goto out;
+ return -EINVAL;
}
+}
- if (!family && !(p->flags & XFRM_STATE_AF_UNSPEC))
- family = p->family;
-
+static int verify_selector_prefixlen(u16 family,
+ const struct xfrm_selector *sel,
+ struct netlink_ext_ack *extack)
+{
switch (family) {
case AF_UNSPEC:
- break;
-
+ return 0;
case AF_INET:
- if (p->sel.prefixlen_d > 32 || p->sel.prefixlen_s > 32) {
- NL_SET_ERR_MSG(extack, "Invalid prefix length in selector (must be <= 32 for IPv4)");
- goto out;
+ if (sel->prefixlen_d > 32 || sel->prefixlen_s > 32) {
+ NL_SET_ERR_MSG(extack,
+ "Invalid prefix length in selector (must be <= 32 for IPv4)");
+ return -EINVAL;
}
-
- break;
-
+ return 0;
case AF_INET6:
#if IS_ENABLED(CONFIG_IPV6)
- if (p->sel.prefixlen_d > 128 || p->sel.prefixlen_s > 128) {
- NL_SET_ERR_MSG(extack, "Invalid prefix length in selector (must be <= 128 for IPv6)");
- goto out;
+ if (sel->prefixlen_d > 128 || sel->prefixlen_s > 128) {
+ NL_SET_ERR_MSG(extack,
+ "Invalid prefix length in selector (must be <= 128 for IPv6)");
+ return -EINVAL;
}
-
- break;
+ return 0;
#else
NL_SET_ERR_MSG(extack, "IPv6 support disabled");
- err = -EAFNOSUPPORT;
- goto out;
+ return -EAFNOSUPPORT;
#endif
-
default:
NL_SET_ERR_MSG(extack, "Invalid address family in selector");
- goto out;
+ return -EINVAL;
}
+}
+
+static int verify_newsa_info(struct xfrm_usersa_info *p,
+ struct nlattr **attrs,
+ struct netlink_ext_ack *extack)
+{
+ int err;
+ u8 sa_dir = nla_get_u8_default(attrs[XFRMA_SA_DIR], 0);
+ u16 family = p->sel.family;
+
+ err = verify_xfrm_family(p->family, extack);
+ if (err)
+ goto out;
+
+ if (!family && !(p->flags & XFRM_STATE_AF_UNSPEC))
+ family = p->family;
+
+ err = verify_selector_prefixlen(family, &p->sel, extack);
+ if (err)
+ goto out;
err = -EINVAL;
switch (p->id.proto) {
@@ -455,18 +479,9 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
err = 0;
if (attrs[XFRMA_MTIMER_THRESH]) {
- if (!attrs[XFRMA_ENCAP]) {
- NL_SET_ERR_MSG(extack, "MTIMER_THRESH attribute can only be set on ENCAP states");
- err = -EINVAL;
- goto out;
- }
-
- if (sa_dir == XFRM_SA_DIR_OUT) {
- NL_SET_ERR_MSG(extack,
- "MTIMER_THRESH attribute should not be set on output SA");
- err = -EINVAL;
+ err = verify_mtimer_thresh(!!attrs[XFRMA_ENCAP], sa_dir, extack);
+ if (err)
goto out;
- }
}
if (sa_dir == XFRM_SA_DIR_OUT) {
@@ -1186,6 +1201,16 @@ static int copy_sec_ctx(struct xfrm_sec_ctx *s, struct sk_buff *skb)
return 0;
}
+static void xso_to_xuo(const struct xfrm_dev_offload *xso,
+ struct xfrm_user_offload *xuo)
+{
+ xuo->ifindex = xso->dev->ifindex;
+ if (xso->dir == XFRM_DEV_OFFLOAD_IN)
+ xuo->flags = XFRM_OFFLOAD_INBOUND;
+ if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
+ xuo->flags |= XFRM_OFFLOAD_PACKET;
+}
+
static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
{
struct xfrm_user_offload *xuo;
@@ -1197,11 +1222,7 @@ static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
xuo = nla_data(attr);
memset(xuo, 0, sizeof(*xuo));
- xuo->ifindex = xso->dev->ifindex;
- if (xso->dir == XFRM_DEV_OFFLOAD_IN)
- xuo->flags = XFRM_OFFLOAD_INBOUND;
- if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
- xuo->flags |= XFRM_OFFLOAD_PACKET;
+ xso_to_xuo(xso, xuo);
return 0;
}
@@ -1326,7 +1347,7 @@ static int copy_to_user_encap(struct xfrm_encap_tmpl *ep, struct sk_buff *skb)
return 0;
}
-static int xfrm_smark_put(struct sk_buff *skb, struct xfrm_mark *m)
+static int xfrm_smark_put(struct sk_buff *skb, const struct xfrm_mark *m)
{
int ret = 0;
@@ -3075,6 +3096,25 @@ nomem:
}
#ifdef CONFIG_XFRM_MIGRATE
+static void copy_from_user_migrate_state(struct xfrm_migrate *ma,
+ const struct xfrm_user_migrate_state *um)
+{
+ memcpy(&ma->old_daddr, &um->id.daddr, sizeof(ma->old_daddr));
+ memcpy(&ma->new_daddr, &um->new_daddr, sizeof(ma->new_daddr));
+ memcpy(&ma->new_saddr, &um->new_saddr, sizeof(ma->new_saddr));
+
+ ma->proto = um->id.proto;
+ ma->new_reqid = um->new_reqid;
+
+ ma->old_family = um->id.family;
+ ma->new_family = um->new_family;
+
+ ma->old_mark = um->old_mark;
+ ma->flags = um->flags;
+ ma->new_sel = &um->new_sel;
+ ma->msg_type = XFRM_MSG_MIGRATE_STATE;
+}
+
static int copy_from_user_migrate(struct xfrm_migrate *ma,
struct xfrm_kmaddress *k,
struct nlattr **attrs, int *num,
@@ -3110,10 +3150,11 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma,
ma->proto = um->proto;
ma->mode = um->mode;
- ma->reqid = um->reqid;
+ ma->old_reqid = um->reqid;
ma->old_family = um->old_family;
ma->new_family = um->new_family;
+ ma->msg_type = XFRM_MSG_MIGRATE;
}
*num = i;
@@ -3124,7 +3165,7 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
struct nlattr **attrs, struct netlink_ext_ack *extack)
{
struct xfrm_userpolicy_id *pi = nlmsg_data(nlh);
- struct xfrm_migrate m[XFRM_MAX_DEPTH];
+ struct xfrm_migrate m[XFRM_MAX_DEPTH] = {};
struct xfrm_kmaddress km, *kmp;
u8 type;
int err;
@@ -3177,7 +3218,298 @@ error:
kfree(xuo);
return err;
}
+
+static int build_migrate_state(struct sk_buff *skb,
+ const struct xfrm_user_migrate_state *um,
+ const struct xfrm_migrate *m,
+ u8 dir, u32 portid, u32 seq)
+{
+ int err;
+ struct nlmsghdr *nlh;
+ struct xfrm_user_migrate_state *hdr;
+
+ nlh = nlmsg_put(skb, portid, seq, XFRM_MSG_MIGRATE_STATE,
+ sizeof(struct xfrm_user_migrate_state), 0);
+ if (!nlh)
+ return -EMSGSIZE;
+
+ hdr = nlmsg_data(nlh);
+ *hdr = *um;
+ hdr->new_sel = *m->new_sel;
+
+ if (m->encap) {
+ err = nla_put(skb, XFRMA_ENCAP, sizeof(*m->encap), m->encap);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (m->xuo) {
+ err = nla_put(skb, XFRMA_OFFLOAD_DEV, sizeof(*m->xuo), m->xuo);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (m->new_mark) {
+ err = nla_put(skb, XFRMA_MARK, sizeof(*m->new_mark),
+ m->new_mark);
+ if (err)
+ goto out_cancel;
+ }
+
+ err = xfrm_smark_put(skb, &m->smark);
+ if (err)
+ goto out_cancel;
+
+ if (m->mapping_maxage) {
+ err = nla_put_u32(skb, XFRMA_MTIMER_THRESH, m->mapping_maxage);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (m->nat_keepalive_interval) {
+ err = nla_put_u32(skb, XFRMA_NAT_KEEPALIVE_INTERVAL,
+ m->nat_keepalive_interval);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (dir) {
+ err = nla_put_u8(skb, XFRMA_SA_DIR, dir);
+ if (err)
+ goto out_cancel;
+ }
+
+ nlmsg_end(skb, nlh);
+ return 0;
+
+out_cancel:
+ nlmsg_cancel(skb, nlh);
+ return err;
+}
+
+static unsigned int xfrm_migrate_state_msgsize(const struct xfrm_migrate *m,
+ u8 dir)
+{
+ return NLMSG_ALIGN(sizeof(struct xfrm_user_migrate_state)) +
+ (m->encap ? nla_total_size(sizeof(struct xfrm_encap_tmpl)) : 0) +
+ (m->xuo ? nla_total_size(sizeof(struct xfrm_user_offload)) : 0) +
+ (m->new_mark ? nla_total_size(sizeof(struct xfrm_mark)) : 0) +
+ ((m->smark.v | m->smark.m) ? nla_total_size(sizeof(u32)) * 2 : 0) +
+ (m->mapping_maxage ? nla_total_size(sizeof(u32)) : 0) +
+ (m->nat_keepalive_interval ? nla_total_size(sizeof(u32)) : 0) +
+ (dir ? nla_total_size(sizeof(u8)) : 0); /* XFRMA_SA_DIR */
+}
+
+static int xfrm_send_migrate_state(struct net *net,
+ const struct xfrm_user_migrate_state *um,
+ const struct xfrm_migrate *m,
+ u8 dir, u32 portid, u32 seq)
+{
+ int err;
+ struct sk_buff *skb;
+
+ skb = nlmsg_new(xfrm_migrate_state_msgsize(m, dir), GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ err = build_migrate_state(skb, um, m, dir, portid, seq);
+ if (err < 0) {
+ kfree_skb(skb);
+ return err;
+ }
+
+ return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE);
+}
+
+static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
+ struct nlattr **attrs, struct netlink_ext_ack *extack)
+{
+ struct xfrm_user_migrate_state *um = nlmsg_data(nlh);
+ struct net *net = sock_net(skb->sk);
+ struct xfrm_user_offload xuo = {};
+ struct xfrm_migrate m = {};
+ struct xfrm_state *xc;
+ struct xfrm_state *x;
+ int err;
+
+ if (!um->id.spi) {
+ NL_SET_ERR_MSG(extack, "Invalid SPI 0x0");
+ return -EINVAL;
+ }
+
+ if (um->reserved) {
+ NL_SET_ERR_MSG(extack, "Reserved field must be zero");
+ return -EINVAL;
+ }
+
+ if (um->flags & ~XFRM_MIGRATE_STATE_KNOWN_FLAGS) {
+ NL_SET_ERR_MSG_FMT(extack, "Unknown flags: 0x%x",
+ um->flags & ~XFRM_MIGRATE_STATE_KNOWN_FLAGS);
+ return -EINVAL;
+ }
+
+ err = verify_xfrm_family(um->new_family, extack);
+ if (err)
+ return err;
+
+ if (!(um->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL)) {
+ err = verify_selector_prefixlen(um->new_sel.family,
+ &um->new_sel, extack);
+ if (err)
+ return err;
+ }
+
+ copy_from_user_migrate_state(&m, um);
+
+ x = xfrm_state_lookup(net, m.old_mark.v & m.old_mark.m,
+ &um->id.daddr, um->id.spi,
+ um->id.proto, um->id.family);
+ if (!x) {
+ NL_SET_ERR_MSG(extack, "Can not find state");
+ return -ESRCH;
+ }
+
+ if (um->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL) {
+ u8 prefixlen = (x->props.family == AF_INET6) ? 128 : 32;
+
+ if (x->sel.prefixlen_s != x->sel.prefixlen_d ||
+ x->sel.prefixlen_d != prefixlen ||
+ !xfrm_addr_equal(&x->sel.daddr, &x->id.daddr, x->props.family) ||
+ !xfrm_addr_equal(&x->sel.saddr, &x->props.saddr, x->props.family)) {
+ NL_SET_ERR_MSG(extack,
+ "SA selector is not a single-host match for SA addresses");
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
+ if (attrs[XFRMA_ENCAP]) {
+ m.encap = nla_data(attrs[XFRMA_ENCAP]);
+ if (m.encap->encap_type == 0) {
+ m.encap = NULL; /* sentinel: remove encap */
+ } else if (m.encap->encap_type != UDP_ENCAP_ESPINUDP) {
+ NL_SET_ERR_MSG(extack, "Unsupported encapsulation type");
+ err = -EINVAL;
+ goto out;
+ }
+ } else {
+ m.encap = x->encap; /* omit-to-inherit */
+ }
+
+ if (attrs[XFRMA_MTIMER_THRESH]) {
+ err = verify_mtimer_thresh(!!m.encap, x->dir, extack);
+ if (err)
+ goto out;
+ }
+
+ if (nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL], 0) && !m.encap) {
+ NL_SET_ERR_MSG(extack,
+ "NAT_KEEPALIVE_INTERVAL requires encapsulation");
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (attrs[XFRMA_OFFLOAD_DEV]) {
+ m.xuo = nla_data(attrs[XFRMA_OFFLOAD_DEV]);
+ } else {
+ bool inherit_offload = !(um->flags & XFRM_MIGRATE_STATE_CLEAR_OFFLOAD);
+
+ if (inherit_offload && x->xso.dev) {
+ xso_to_xuo(&x->xso, &xuo);
+ m.xuo = &xuo;
+ }
+ }
+
+ if (attrs[XFRMA_MARK])
+ m.new_mark = nla_data(attrs[XFRMA_MARK]);
+
+ if (attrs[XFRMA_SET_MARK])
+ xfrm_smark_init(attrs, &m.smark);
+ else
+ m.smark = x->props.smark;
+
+ m.mapping_maxage = nla_get_u32_default(attrs[XFRMA_MTIMER_THRESH],
+ x->mapping_maxage);
+ m.nat_keepalive_interval = nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL],
+ x->nat_keepalive_interval);
+
+ if (m.new_family != um->id.family ||
+ !xfrm_addr_equal(&m.new_daddr, &um->id.daddr, um->id.family)) {
+ u32 new_mark_key = m.new_mark ? m.new_mark->v & m.new_mark->m :
+ m.old_mark.v & m.old_mark.m;
+ struct xfrm_state *x_new;
+
+ x_new = xfrm_state_lookup(net, new_mark_key, &m.new_daddr,
+ um->id.spi, um->id.proto, m.new_family);
+ if (x_new) {
+ xfrm_state_put(x_new);
+ NL_SET_ERR_MSG(extack, "New SA tuple already occupied");
+ err = -EEXIST;
+ goto out;
+ }
+ }
+
+ xc = xfrm_state_migrate_create(x, &m, net, extack);
+ if (!xc) {
+ NL_SET_ERR_MSG_WEAK(extack, "State migration clone failed");
+ err = -EINVAL;
+ goto out;
+ }
+
+ spin_lock_bh(&x->lock);
+ if (x->km.state != XFRM_STATE_VALID) {
+ spin_unlock_bh(&x->lock);
+ NL_SET_ERR_MSG(extack, "State already deleted");
+ err = -ESRCH;
+ goto out_xc;
+ }
+ xfrm_migrate_sync(xc, x); /* to prevent SN/IV reuse */
+ __xfrm_state_delete(x);
+ spin_unlock_bh(&x->lock);
+
+ err = xfrm_state_migrate_install(x, xc, &m, extack);
+ if (err < 0) {
+ /*
+ * Should not occur: pre-check above ensures the new tuple is
+ * free under xfrm_cfg_mutex. Both SAs are gone if it does;
+ * restoring x would risk SN/IV reuse.
+ */
+ goto out;
+ }
+
+ /* Restore encap cleared by sentinel (type=0) during migration. */
+ if (attrs[XFRMA_ENCAP])
+ m.encap = nla_data(attrs[XFRMA_ENCAP]);
+
+ m.new_sel = &xc->sel;
+ m.mapping_maxage = xc->mapping_maxage;
+ m.nat_keepalive_interval = xc->nat_keepalive_interval;
+
+ err = xfrm_send_migrate_state(net, um, &m, xc->dir,
+ nlh->nlmsg_pid, nlh->nlmsg_seq);
+ if (err < 0) {
+ NL_SET_ERR_MSG(extack, "Failed to send migration notification");
+ err = 0;
+ }
+
+out:
+ xfrm_state_put(x);
+ return err;
+out_xc:
+ xc->km.state = XFRM_STATE_DEAD;
+ xfrm_state_put(xc);
+ xfrm_state_put(x);
+ return err;
+}
+
#else
+static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
+ struct nlattr **attrs, struct netlink_ext_ack *extack)
+{
+ NL_SET_ERR_MSG(extack, "XFRM_MSG_MIGRATE_STATE is not supported");
+ return -ENOPROTOOPT;
+}
+
static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
struct nlattr **attrs, struct netlink_ext_ack *extack)
{
@@ -3193,7 +3525,7 @@ static int copy_to_user_migrate(const struct xfrm_migrate *m, struct sk_buff *sk
memset(&um, 0, sizeof(um));
um.proto = m->proto;
um.mode = m->mode;
- um.reqid = m->reqid;
+ um.reqid = m->old_reqid;
um.old_family = m->old_family;
memcpy(&um.old_daddr, &m->old_daddr, sizeof(um.old_daddr));
memcpy(&um.old_saddr, &m->old_saddr, sizeof(um.old_saddr));
@@ -3330,6 +3662,7 @@ const int xfrm_msg_min[XFRM_NR_MSGTYPES] = {
[XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = sizeof(u32),
[XFRM_MSG_SETDEFAULT - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_default),
[XFRM_MSG_GETDEFAULT - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_default),
+ [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_migrate_state),
};
EXPORT_SYMBOL_GPL(xfrm_msg_min);
@@ -3423,6 +3756,7 @@ static const struct xfrm_link {
[XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo },
[XFRM_MSG_SETDEFAULT - XFRM_MSG_BASE] = { .doit = xfrm_set_default },
[XFRM_MSG_GETDEFAULT - XFRM_MSG_BASE] = { .doit = xfrm_get_default },
+ [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = { .doit = xfrm_do_migrate_state },
};
static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
@@ -3454,6 +3788,30 @@ static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
}
}
+ if (type == XFRM_MSG_MIGRATE_STATE) {
+ int i;
+
+ for (i = 0; i <= XFRMA_MAX; i++) {
+ if (!attrs[i])
+ continue;
+
+ switch (i) {
+ case XFRMA_MARK:
+ case XFRMA_ENCAP:
+ case XFRMA_OFFLOAD_DEV:
+ case XFRMA_SET_MARK:
+ case XFRMA_SET_MARK_MASK:
+ case XFRMA_MTIMER_THRESH:
+ case XFRMA_NAT_KEEPALIVE_INTERVAL:
+ break;
+ default:
+ NL_SET_ERR_MSG_ATTR(extack, attrs[i],
+ "Unsupported attribute in XFRM_MSG_MIGRATE_STATE");
+ return -EINVAL;
+ }
+ }
+ }
+
return 0;
}
diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c
index 2c0b07f9fbbd..655d2616c9d2 100644
--- a/security/selinux/nlmsgtab.c
+++ b/security/selinux/nlmsgtab.c
@@ -128,6 +128,7 @@ static const struct nlmsg_perm nlmsg_xfrm_perms[] = {
{ XFRM_MSG_MAPPING, NETLINK_XFRM_SOCKET__NLMSG_READ },
{ XFRM_MSG_SETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
{ XFRM_MSG_GETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_READ },
+ { XFRM_MSG_MIGRATE_STATE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
};
static const struct nlmsg_perm nlmsg_audit_perms[] = {
@@ -203,7 +204,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm)
* structures at the top of this file with the new mappings
* before updating the BUILD_BUG_ON() macro!
*/
- BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_GETDEFAULT);
+ BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_MIGRATE_STATE);
if (selinux_policycap_netlink_xperm()) {
*perm = NETLINK_XFRM_SOCKET__NLMSG;