diff options
| author | Steffen Klassert <steffen.klassert@secunet.com> | 2026-06-09 17:02:12 +0300 |
|---|---|---|
| committer | Steffen Klassert <steffen.klassert@secunet.com> | 2026-06-09 17:02:12 +0300 |
| commit | 355f808d8a11fa69b19dfd8811bc87d97830f5d6 (patch) | |
| tree | eadc95f807ba7af7192412b5c2a0e6386a7564c1 | |
| parent | 41c4d3b26f5e23609cd4b5ca561a399a097daabe (diff) | |
| parent | c13c0cc6f52e491186f6521dc80031c35737162d (diff) | |
| download | linux-355f808d8a11fa69b19dfd8811bc87d97830f5d6.tar.xz | |
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>
| -rw-r--r-- | Documentation/networking/xfrm/index.rst | 1 | ||||
| -rw-r--r-- | Documentation/networking/xfrm/xfrm_migrate_state.rst | 274 | ||||
| -rw-r--r-- | include/net/xfrm.h | 78 | ||||
| -rw-r--r-- | include/uapi/linux/xfrm.h | 25 | ||||
| -rw-r--r-- | net/ipv4/ipcomp.c | 2 | ||||
| -rw-r--r-- | net/ipv6/ipcomp6.c | 2 | ||||
| -rw-r--r-- | net/key/af_key.c | 12 | ||||
| -rw-r--r-- | net/xfrm/xfrm_compat.c | 5 | ||||
| -rw-r--r-- | net/xfrm/xfrm_device.c | 2 | ||||
| -rw-r--r-- | net/xfrm/xfrm_policy.c | 25 | ||||
| -rw-r--r-- | net/xfrm/xfrm_state.c | 144 | ||||
| -rw-r--r-- | net/xfrm/xfrm_user.c | 468 | ||||
| -rw-r--r-- | security/selinux/nlmsgtab.c | 3 |
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; |
