Merge branch 'xfrm: XFRM_MSG_MIGRATE_STATE new netlink message'

Antony Antony says:

====================
The current XFRM_MSG_MIGRATE interface is tightly coupled to policy and
SA migration, and it lacks the information required to reliably migrate
individual SAs. This makes it unsuitable for IKEv2 deployments,
dual-stack setups (IPv4/IPv6), and scenarios where policies are managed
externally (e.g., by daemons other than the IKE daemon).

Mandatory SA selector list
The current API requires a non-empty SA selector list, which does not
reflect the IKEv2 use case.
A single Child SA may correspond to multiple policies,
and SA discovery already occurs via address and reqid matching. With
dual-stack Child SAs this leads to excessive churn: the current method
would have to be called up to six times (in/out/fwd × v4/v6) on SA,
while the new method only requires two calls.

Selectors lack SPI (and marks)
XFRM_MSG_MIGRATE cannot uniquely identify an SA when multiple SAs share
the same policies (per-CPU SAs, SELinux label-based SAs, etc.). Without
the SPI, the kernel may update the wrong SA instance.

Reqid cannot be changed
Some implementations allocate reqids based on traffic selectors. In
host-to-host or selector-changing scenarios, the reqid must change,
which the current API cannot express.

Because strongSwan and other implementations manage policies
independently of the kernel, an interface that updates only a specific
SA - with complete and unambiguous identification - is required.

SA Selector, x->sel, can't be changed, especially Transport mode.

XFRM_MSG_MIGRATE_STATE provides that interface. It supports migration
of a single SA via xfrm_usersa_id (including SPI) and we fix
encap removal in this patch set, reqid updates, address changes,
and other SA-specific parameters. It avoids the structural limitations
of XFRM_MSG_MIGRATE and provides a simpler, extensible mechanism for
precise per-SA migration without involving policies.
This method also allows migtrating SA selectors typically used with
host-to-host in Transport mode.

New migration steps: first install block policy, remove the old policy,
call XFRM_MSG_MIGRATE_STATE for each state, then re-install the
policies and remove the block policy.

If the target SA tuple (daddr, SPI, proto, family) is already
occupied, the operation returns -EEXIST. In this case the original
SA is not preserved. Userspace must handle -EEXIST by
re-establishing the SA at the IKE level and manage policies.
====================

Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
This commit is contained in:
Steffen Klassert
2026-06-09 16:02:12 +02:00
13 changed files with 918 additions and 153 deletions
+1
View File
@@ -9,5 +9,6 @@ XFRM Framework
xfrm_device
xfrm_proc
xfrm_migrate_state
xfrm_sync
xfrm_sysctl
@@ -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.
+62 -16
View File
@@ -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_esn = kmemdup(orig->replay_esn,
x->replay = orig->replay;
x->preplay = orig->preplay;
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;
}
+25
View File
@@ -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;
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
+6 -6
View File
@@ -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;
}
+4 -1
View File
@@ -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:
+1 -1
View File
@@ -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;
+22 -3
View File
@@ -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++;
+95 -57
View File
@@ -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)
goto error;
/* configure the hardware if offload is requested */
if (xuo && xfrm_dev_state_add(net, xc, 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;
}
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 (m->xuo && xfrm_dev_state_add(net, xc, m->xuo, extack))
goto error;
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;
+424 -66
View File
@@ -248,6 +248,72 @@ static inline int verify_replay(struct xfrm_usersa_info *p,
return 0;
}
static int verify_mtimer_thresh(bool has_encap, u8 dir,
struct netlink_ext_ack *extack)
{
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;
}
static int verify_xfrm_family(u16 family, struct netlink_ext_ack *extack)
{
switch (family) {
case AF_INET:
return 0;
case AF_INET6:
#if IS_ENABLED(CONFIG_IPV6)
return 0;
#else
NL_SET_ERR_MSG(extack, "IPv6 support disabled");
return -EAFNOSUPPORT;
#endif
default:
NL_SET_ERR_MSG(extack, "Invalid address family");
return -EINVAL;
}
}
static int verify_selector_prefixlen(u16 family,
const struct xfrm_selector *sel,
struct netlink_ext_ack *extack)
{
switch (family) {
case AF_UNSPEC:
return 0;
case AF_INET:
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;
}
return 0;
case AF_INET6:
#if IS_ENABLED(CONFIG_IPV6)
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;
}
return 0;
#else
NL_SET_ERR_MSG(extack, "IPv6 support disabled");
return -EAFNOSUPPORT;
#endif
default:
NL_SET_ERR_MSG(extack, "Invalid address family in selector");
return -EINVAL;
}
}
static int verify_newsa_info(struct xfrm_usersa_info *p,
struct nlattr **attrs,
struct netlink_ext_ack *extack)
@@ -256,58 +322,16 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
u8 sa_dir = nla_get_u8_default(attrs[XFRMA_SA_DIR], 0);
u16 family = p->sel.family;
err = -EINVAL;
switch (p->family) {
case AF_INET:
break;
case AF_INET6:
#if IS_ENABLED(CONFIG_IPV6)
break;
#else
err = -EAFNOSUPPORT;
NL_SET_ERR_MSG(extack, "IPv6 support disabled");
err = verify_xfrm_family(p->family, extack);
if (err)
goto out;
#endif
default:
NL_SET_ERR_MSG(extack, "Invalid address family");
goto out;
}
if (!family && !(p->flags & XFRM_STATE_AF_UNSPEC))
family = p->family;
switch (family) {
case AF_UNSPEC:
break;
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;
}
break;
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;
}
break;
#else
NL_SET_ERR_MSG(extack, "IPv6 support disabled");
err = -EAFNOSUPPORT;
err = verify_selector_prefixlen(family, &p->sel, extack);
if (err)
goto out;
#endif
default:
NL_SET_ERR_MSG(extack, "Invalid address family in selector");
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;
err = verify_mtimer_thresh(!!attrs[XFRMA_ENCAP], sa_dir, extack);
if (err)
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;
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;
}
+2 -1
View File
@@ -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;