| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * XFRM compat layer | 
 |  * Author: Dmitry Safonov <dima@arista.com> | 
 |  * Based on code and translator idea by: Florian Westphal <fw@strlen.de> | 
 |  */ | 
 | #include <linux/compat.h> | 
 | #include <linux/nospec.h> | 
 | #include <linux/xfrm.h> | 
 | #include <net/xfrm.h> | 
 |  | 
 | struct compat_xfrm_lifetime_cfg { | 
 | 	compat_u64 soft_byte_limit, hard_byte_limit; | 
 | 	compat_u64 soft_packet_limit, hard_packet_limit; | 
 | 	compat_u64 soft_add_expires_seconds, hard_add_expires_seconds; | 
 | 	compat_u64 soft_use_expires_seconds, hard_use_expires_seconds; | 
 | }; /* same size on 32bit, but only 4 byte alignment required */ | 
 |  | 
 | struct compat_xfrm_lifetime_cur { | 
 | 	compat_u64 bytes, packets, add_time, use_time; | 
 | }; /* same size on 32bit, but only 4 byte alignment required */ | 
 |  | 
 | struct compat_xfrm_userpolicy_info { | 
 | 	struct xfrm_selector sel; | 
 | 	struct compat_xfrm_lifetime_cfg lft; | 
 | 	struct compat_xfrm_lifetime_cur curlft; | 
 | 	__u32 priority, index; | 
 | 	u8 dir, action, flags, share; | 
 | 	/* 4 bytes additional padding on 64bit */ | 
 | }; | 
 |  | 
 | struct compat_xfrm_usersa_info { | 
 | 	struct xfrm_selector sel; | 
 | 	struct xfrm_id id; | 
 | 	xfrm_address_t saddr; | 
 | 	struct compat_xfrm_lifetime_cfg lft; | 
 | 	struct compat_xfrm_lifetime_cur curlft; | 
 | 	struct xfrm_stats stats; | 
 | 	__u32 seq, reqid; | 
 | 	u16 family; | 
 | 	u8 mode, replay_window, flags; | 
 | 	/* 4 bytes additional padding on 64bit */ | 
 | }; | 
 |  | 
 | struct compat_xfrm_user_acquire { | 
 | 	struct xfrm_id id; | 
 | 	xfrm_address_t saddr; | 
 | 	struct xfrm_selector sel; | 
 | 	struct compat_xfrm_userpolicy_info policy; | 
 | 	/* 4 bytes additional padding on 64bit */ | 
 | 	__u32 aalgos, ealgos, calgos, seq; | 
 | }; | 
 |  | 
 | struct compat_xfrm_userspi_info { | 
 | 	struct compat_xfrm_usersa_info info; | 
 | 	/* 4 bytes additional padding on 64bit */ | 
 | 	__u32 min, max; | 
 | }; | 
 |  | 
 | struct compat_xfrm_user_expire { | 
 | 	struct compat_xfrm_usersa_info state; | 
 | 	/* 8 bytes additional padding on 64bit */ | 
 | 	u8 hard; | 
 | }; | 
 |  | 
 | struct compat_xfrm_user_polexpire { | 
 | 	struct compat_xfrm_userpolicy_info pol; | 
 | 	/* 8 bytes additional padding on 64bit */ | 
 | 	u8 hard; | 
 | }; | 
 |  | 
 | #define XMSGSIZE(type) sizeof(struct type) | 
 |  | 
 | static const int compat_msg_min[XFRM_NR_MSGTYPES] = { | 
 | 	[XFRM_MSG_NEWSA       - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_usersa_info), | 
 | 	[XFRM_MSG_DELSA       - XFRM_MSG_BASE] = XMSGSIZE(xfrm_usersa_id), | 
 | 	[XFRM_MSG_GETSA       - XFRM_MSG_BASE] = XMSGSIZE(xfrm_usersa_id), | 
 | 	[XFRM_MSG_NEWPOLICY   - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_userpolicy_info), | 
 | 	[XFRM_MSG_DELPOLICY   - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_id), | 
 | 	[XFRM_MSG_GETPOLICY   - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_id), | 
 | 	[XFRM_MSG_ALLOCSPI    - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_userspi_info), | 
 | 	[XFRM_MSG_ACQUIRE     - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_user_acquire), | 
 | 	[XFRM_MSG_EXPIRE      - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_user_expire), | 
 | 	[XFRM_MSG_UPDPOLICY   - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_userpolicy_info), | 
 | 	[XFRM_MSG_UPDSA       - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_usersa_info), | 
 | 	[XFRM_MSG_POLEXPIRE   - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_user_polexpire), | 
 | 	[XFRM_MSG_FLUSHSA     - XFRM_MSG_BASE] = XMSGSIZE(xfrm_usersa_flush), | 
 | 	[XFRM_MSG_FLUSHPOLICY - XFRM_MSG_BASE] = 0, | 
 | 	[XFRM_MSG_NEWAE       - XFRM_MSG_BASE] = XMSGSIZE(xfrm_aevent_id), | 
 | 	[XFRM_MSG_GETAE       - XFRM_MSG_BASE] = XMSGSIZE(xfrm_aevent_id), | 
 | 	[XFRM_MSG_REPORT      - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_report), | 
 | 	[XFRM_MSG_MIGRATE     - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_id), | 
 | 	[XFRM_MSG_NEWSADINFO  - XFRM_MSG_BASE] = sizeof(u32), | 
 | 	[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) | 
 | }; | 
 |  | 
 | static const struct nla_policy compat_policy[XFRMA_MAX+1] = { | 
 | 	[XFRMA_UNSPEC]          = { .strict_start_type = XFRMA_SA_DIR }, | 
 | 	[XFRMA_SA]		= { .len = XMSGSIZE(compat_xfrm_usersa_info)}, | 
 | 	[XFRMA_POLICY]		= { .len = XMSGSIZE(compat_xfrm_userpolicy_info)}, | 
 | 	[XFRMA_LASTUSED]	= { .type = NLA_U64}, | 
 | 	[XFRMA_ALG_AUTH_TRUNC]	= { .len = sizeof(struct xfrm_algo_auth)}, | 
 | 	[XFRMA_ALG_AEAD]	= { .len = sizeof(struct xfrm_algo_aead) }, | 
 | 	[XFRMA_ALG_AUTH]	= { .len = sizeof(struct xfrm_algo) }, | 
 | 	[XFRMA_ALG_CRYPT]	= { .len = sizeof(struct xfrm_algo) }, | 
 | 	[XFRMA_ALG_COMP]	= { .len = sizeof(struct xfrm_algo) }, | 
 | 	[XFRMA_ENCAP]		= { .len = sizeof(struct xfrm_encap_tmpl) }, | 
 | 	[XFRMA_TMPL]		= { .len = sizeof(struct xfrm_user_tmpl) }, | 
 | 	[XFRMA_SEC_CTX]		= { .len = sizeof(struct xfrm_user_sec_ctx) }, | 
 | 	[XFRMA_LTIME_VAL]	= { .len = sizeof(struct xfrm_lifetime_cur) }, | 
 | 	[XFRMA_REPLAY_VAL]	= { .len = sizeof(struct xfrm_replay_state) }, | 
 | 	[XFRMA_REPLAY_THRESH]	= { .type = NLA_U32 }, | 
 | 	[XFRMA_ETIMER_THRESH]	= { .type = NLA_U32 }, | 
 | 	[XFRMA_SRCADDR]		= { .len = sizeof(xfrm_address_t) }, | 
 | 	[XFRMA_COADDR]		= { .len = sizeof(xfrm_address_t) }, | 
 | 	[XFRMA_POLICY_TYPE]	= { .len = sizeof(struct xfrm_userpolicy_type)}, | 
 | 	[XFRMA_MIGRATE]		= { .len = sizeof(struct xfrm_user_migrate) }, | 
 | 	[XFRMA_KMADDRESS]	= { .len = sizeof(struct xfrm_user_kmaddress) }, | 
 | 	[XFRMA_MARK]		= { .len = sizeof(struct xfrm_mark) }, | 
 | 	[XFRMA_TFCPAD]		= { .type = NLA_U32 }, | 
 | 	[XFRMA_REPLAY_ESN_VAL]	= { .len = sizeof(struct xfrm_replay_state_esn) }, | 
 | 	[XFRMA_SA_EXTRA_FLAGS]	= { .type = NLA_U32 }, | 
 | 	[XFRMA_PROTO]		= { .type = NLA_U8 }, | 
 | 	[XFRMA_ADDRESS_FILTER]	= { .len = sizeof(struct xfrm_address_filter) }, | 
 | 	[XFRMA_OFFLOAD_DEV]	= { .len = sizeof(struct xfrm_user_offload) }, | 
 | 	[XFRMA_SET_MARK]	= { .type = NLA_U32 }, | 
 | 	[XFRMA_SET_MARK_MASK]	= { .type = NLA_U32 }, | 
 | 	[XFRMA_IF_ID]		= { .type = NLA_U32 }, | 
 | 	[XFRMA_MTIMER_THRESH]	= { .type = NLA_U32 }, | 
 | 	[XFRMA_SA_DIR]          = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT), | 
 | 	[XFRMA_NAT_KEEPALIVE_INTERVAL]	= { .type = NLA_U32 }, | 
 | 	[XFRMA_SA_PCPU]		= { .type = NLA_U32 }, | 
 | }; | 
 |  | 
 | static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb, | 
 | 			const struct nlmsghdr *nlh_src, u16 type) | 
 | { | 
 | 	int payload = compat_msg_min[type]; | 
 | 	int src_len = xfrm_msg_min[type]; | 
 | 	struct nlmsghdr *nlh_dst; | 
 |  | 
 | 	/* Compat messages are shorter or equal to native (+padding) */ | 
 | 	if (WARN_ON_ONCE(src_len < payload)) | 
 | 		return ERR_PTR(-EMSGSIZE); | 
 |  | 
 | 	nlh_dst = nlmsg_put(skb, nlh_src->nlmsg_pid, nlh_src->nlmsg_seq, | 
 | 			    nlh_src->nlmsg_type, payload, nlh_src->nlmsg_flags); | 
 | 	if (!nlh_dst) | 
 | 		return ERR_PTR(-EMSGSIZE); | 
 |  | 
 | 	memset(nlmsg_data(nlh_dst), 0, payload); | 
 |  | 
 | 	switch (nlh_src->nlmsg_type) { | 
 | 	/* Compat message has the same layout as native */ | 
 | 	case XFRM_MSG_DELSA: | 
 | 	case XFRM_MSG_DELPOLICY: | 
 | 	case XFRM_MSG_FLUSHSA: | 
 | 	case XFRM_MSG_FLUSHPOLICY: | 
 | 	case XFRM_MSG_NEWAE: | 
 | 	case XFRM_MSG_REPORT: | 
 | 	case XFRM_MSG_MIGRATE: | 
 | 	case XFRM_MSG_NEWSADINFO: | 
 | 	case XFRM_MSG_NEWSPDINFO: | 
 | 	case XFRM_MSG_MAPPING: | 
 | 		WARN_ON_ONCE(src_len != payload); | 
 | 		memcpy(nlmsg_data(nlh_dst), nlmsg_data(nlh_src), src_len); | 
 | 		break; | 
 | 	/* 4 byte alignment for trailing u64 on native, but not on compat */ | 
 | 	case XFRM_MSG_NEWSA: | 
 | 	case XFRM_MSG_NEWPOLICY: | 
 | 	case XFRM_MSG_UPDSA: | 
 | 	case XFRM_MSG_UPDPOLICY: | 
 | 		WARN_ON_ONCE(src_len != payload + 4); | 
 | 		memcpy(nlmsg_data(nlh_dst), nlmsg_data(nlh_src), payload); | 
 | 		break; | 
 | 	case XFRM_MSG_EXPIRE: { | 
 | 		const struct xfrm_user_expire *src_ue  = nlmsg_data(nlh_src); | 
 | 		struct compat_xfrm_user_expire *dst_ue = nlmsg_data(nlh_dst); | 
 |  | 
 | 		/* compat_xfrm_user_expire has 4-byte smaller state */ | 
 | 		memcpy(dst_ue, src_ue, sizeof(dst_ue->state)); | 
 | 		dst_ue->hard = src_ue->hard; | 
 | 		break; | 
 | 	} | 
 | 	case XFRM_MSG_ACQUIRE: { | 
 | 		const struct xfrm_user_acquire *src_ua  = nlmsg_data(nlh_src); | 
 | 		struct compat_xfrm_user_acquire *dst_ua = nlmsg_data(nlh_dst); | 
 |  | 
 | 		memcpy(dst_ua, src_ua, offsetof(struct compat_xfrm_user_acquire, aalgos)); | 
 | 		dst_ua->aalgos = src_ua->aalgos; | 
 | 		dst_ua->ealgos = src_ua->ealgos; | 
 | 		dst_ua->calgos = src_ua->calgos; | 
 | 		dst_ua->seq    = src_ua->seq; | 
 | 		break; | 
 | 	} | 
 | 	case XFRM_MSG_POLEXPIRE: { | 
 | 		const struct xfrm_user_polexpire *src_upe  = nlmsg_data(nlh_src); | 
 | 		struct compat_xfrm_user_polexpire *dst_upe = nlmsg_data(nlh_dst); | 
 |  | 
 | 		/* compat_xfrm_user_polexpire has 4-byte smaller state */ | 
 | 		memcpy(dst_upe, src_upe, sizeof(dst_upe->pol)); | 
 | 		dst_upe->hard = src_upe->hard; | 
 | 		break; | 
 | 	} | 
 | 	case XFRM_MSG_ALLOCSPI: { | 
 | 		const struct xfrm_userspi_info *src_usi = nlmsg_data(nlh_src); | 
 | 		struct compat_xfrm_userspi_info *dst_usi = nlmsg_data(nlh_dst); | 
 |  | 
 | 		/* compat_xfrm_user_polexpire has 4-byte smaller state */ | 
 | 		memcpy(dst_usi, src_usi, sizeof(src_usi->info)); | 
 | 		dst_usi->min = src_usi->min; | 
 | 		dst_usi->max = src_usi->max; | 
 | 		break; | 
 | 	} | 
 | 	/* Not being sent by kernel */ | 
 | 	case XFRM_MSG_GETSA: | 
 | 	case XFRM_MSG_GETPOLICY: | 
 | 	case XFRM_MSG_GETAE: | 
 | 	case XFRM_MSG_GETSADINFO: | 
 | 	case XFRM_MSG_GETSPDINFO: | 
 | 	default: | 
 | 		pr_warn_once("unsupported nlmsg_type %d\n", nlh_src->nlmsg_type); | 
 | 		return ERR_PTR(-EOPNOTSUPP); | 
 | 	} | 
 |  | 
 | 	return nlh_dst; | 
 | } | 
 |  | 
 | static int xfrm_nla_cpy(struct sk_buff *dst, const struct nlattr *src, int len) | 
 | { | 
 | 	return nla_put(dst, src->nla_type, len, nla_data(src)); | 
 | } | 
 |  | 
 | static int xfrm_xlate64_attr(struct sk_buff *dst, const struct nlattr *src) | 
 | { | 
 | 	switch (src->nla_type) { | 
 | 	case XFRMA_PAD: | 
 | 		/* Ignore */ | 
 | 		return 0; | 
 | 	case XFRMA_UNSPEC: | 
 | 	case XFRMA_ALG_AUTH: | 
 | 	case XFRMA_ALG_CRYPT: | 
 | 	case XFRMA_ALG_COMP: | 
 | 	case XFRMA_ENCAP: | 
 | 	case XFRMA_TMPL: | 
 | 		return xfrm_nla_cpy(dst, src, nla_len(src)); | 
 | 	case XFRMA_SA: | 
 | 		return xfrm_nla_cpy(dst, src, XMSGSIZE(compat_xfrm_usersa_info)); | 
 | 	case XFRMA_POLICY: | 
 | 		return xfrm_nla_cpy(dst, src, XMSGSIZE(compat_xfrm_userpolicy_info)); | 
 | 	case XFRMA_SEC_CTX: | 
 | 		return xfrm_nla_cpy(dst, src, nla_len(src)); | 
 | 	case XFRMA_LTIME_VAL: | 
 | 		return nla_put_64bit(dst, src->nla_type, nla_len(src), | 
 | 			nla_data(src), XFRMA_PAD); | 
 | 	case XFRMA_REPLAY_VAL: | 
 | 	case XFRMA_REPLAY_THRESH: | 
 | 	case XFRMA_ETIMER_THRESH: | 
 | 	case XFRMA_SRCADDR: | 
 | 	case XFRMA_COADDR: | 
 | 		return xfrm_nla_cpy(dst, src, nla_len(src)); | 
 | 	case XFRMA_LASTUSED: | 
 | 		return nla_put_64bit(dst, src->nla_type, nla_len(src), | 
 | 			nla_data(src), XFRMA_PAD); | 
 | 	case XFRMA_POLICY_TYPE: | 
 | 	case XFRMA_MIGRATE: | 
 | 	case XFRMA_ALG_AEAD: | 
 | 	case XFRMA_KMADDRESS: | 
 | 	case XFRMA_ALG_AUTH_TRUNC: | 
 | 	case XFRMA_MARK: | 
 | 	case XFRMA_TFCPAD: | 
 | 	case XFRMA_REPLAY_ESN_VAL: | 
 | 	case XFRMA_SA_EXTRA_FLAGS: | 
 | 	case XFRMA_PROTO: | 
 | 	case XFRMA_ADDRESS_FILTER: | 
 | 	case XFRMA_OFFLOAD_DEV: | 
 | 	case XFRMA_SET_MARK: | 
 | 	case XFRMA_SET_MARK_MASK: | 
 | 	case XFRMA_IF_ID: | 
 | 	case XFRMA_MTIMER_THRESH: | 
 | 	case XFRMA_SA_DIR: | 
 | 	case XFRMA_NAT_KEEPALIVE_INTERVAL: | 
 | 	case XFRMA_SA_PCPU: | 
 | 	case XFRMA_IPTFS_DROP_TIME: | 
 | 	case XFRMA_IPTFS_REORDER_WINDOW: | 
 | 	case XFRMA_IPTFS_DONT_FRAG: | 
 | 	case XFRMA_IPTFS_INIT_DELAY: | 
 | 	case XFRMA_IPTFS_MAX_QSIZE: | 
 | 	case XFRMA_IPTFS_PKT_SIZE: | 
 | 		return xfrm_nla_cpy(dst, src, nla_len(src)); | 
 | 	default: | 
 | 		BUILD_BUG_ON(XFRMA_MAX != XFRMA_IPTFS_PKT_SIZE); | 
 | 		pr_warn_once("unsupported nla_type %d\n", src->nla_type); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 | } | 
 |  | 
 | /* Take kernel-built (64bit layout) and create 32bit layout for userspace */ | 
 | static int xfrm_xlate64(struct sk_buff *dst, const struct nlmsghdr *nlh_src) | 
 | { | 
 | 	u16 type = nlh_src->nlmsg_type - XFRM_MSG_BASE; | 
 | 	const struct nlattr *nla, *attrs; | 
 | 	struct nlmsghdr *nlh_dst; | 
 | 	int len, remaining; | 
 |  | 
 | 	nlh_dst = xfrm_nlmsg_put_compat(dst, nlh_src, type); | 
 | 	if (IS_ERR(nlh_dst)) | 
 | 		return PTR_ERR(nlh_dst); | 
 |  | 
 | 	attrs = nlmsg_attrdata(nlh_src, xfrm_msg_min[type]); | 
 | 	len = nlmsg_attrlen(nlh_src, xfrm_msg_min[type]); | 
 |  | 
 | 	nla_for_each_attr(nla, attrs, len, remaining) { | 
 | 		int err; | 
 |  | 
 | 		switch (nlh_src->nlmsg_type) { | 
 | 		case XFRM_MSG_NEWSPDINFO: | 
 | 			err = xfrm_nla_cpy(dst, nla, nla_len(nla)); | 
 | 			break; | 
 | 		default: | 
 | 			err = xfrm_xlate64_attr(dst, nla); | 
 | 			break; | 
 | 		} | 
 | 		if (err) | 
 | 			return err; | 
 | 	} | 
 |  | 
 | 	nlmsg_end(dst, nlh_dst); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int xfrm_alloc_compat(struct sk_buff *skb, const struct nlmsghdr *nlh_src) | 
 | { | 
 | 	u16 type = nlh_src->nlmsg_type - XFRM_MSG_BASE; | 
 | 	struct sk_buff *new = NULL; | 
 | 	int err; | 
 |  | 
 | 	if (type >= ARRAY_SIZE(xfrm_msg_min)) { | 
 | 		pr_warn_once("unsupported nlmsg_type %d\n", nlh_src->nlmsg_type); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 |  | 
 | 	if (skb_shinfo(skb)->frag_list == NULL) { | 
 | 		new = alloc_skb(skb->len + skb_tailroom(skb), GFP_ATOMIC); | 
 | 		if (!new) | 
 | 			return -ENOMEM; | 
 | 		skb_shinfo(skb)->frag_list = new; | 
 | 	} | 
 |  | 
 | 	err = xfrm_xlate64(skb_shinfo(skb)->frag_list, nlh_src); | 
 | 	if (err) { | 
 | 		if (new) { | 
 | 			kfree_skb(new); | 
 | 			skb_shinfo(skb)->frag_list = NULL; | 
 | 		} | 
 | 		return err; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* Calculates len of translated 64-bit message. */ | 
 | static size_t xfrm_user_rcv_calculate_len64(const struct nlmsghdr *src, | 
 | 					    struct nlattr *attrs[XFRMA_MAX + 1], | 
 | 					    int maxtype) | 
 | { | 
 | 	size_t len = nlmsg_len(src); | 
 |  | 
 | 	switch (src->nlmsg_type) { | 
 | 	case XFRM_MSG_NEWSA: | 
 | 	case XFRM_MSG_NEWPOLICY: | 
 | 	case XFRM_MSG_ALLOCSPI: | 
 | 	case XFRM_MSG_ACQUIRE: | 
 | 	case XFRM_MSG_UPDPOLICY: | 
 | 	case XFRM_MSG_UPDSA: | 
 | 		len += 4; | 
 | 		break; | 
 | 	case XFRM_MSG_EXPIRE: | 
 | 	case XFRM_MSG_POLEXPIRE: | 
 | 		len += 8; | 
 | 		break; | 
 | 	case XFRM_MSG_NEWSPDINFO: | 
 | 		/* attirbutes are xfrm_spdattr_type_t, not xfrm_attr_type_t */ | 
 | 		return len; | 
 | 	default: | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	/* Unexpected for anything, but XFRM_MSG_NEWSPDINFO, please | 
 | 	 * correct both 64=>32-bit and 32=>64-bit translators to copy | 
 | 	 * new attributes. | 
 | 	 */ | 
 | 	if (WARN_ON_ONCE(maxtype)) | 
 | 		return len; | 
 |  | 
 | 	if (attrs[XFRMA_SA]) | 
 | 		len += 4; | 
 | 	if (attrs[XFRMA_POLICY]) | 
 | 		len += 4; | 
 |  | 
 | 	/* XXX: some attrs may need to be realigned | 
 | 	 * if !CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS | 
 | 	 */ | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static int xfrm_attr_cpy32(void *dst, size_t *pos, const struct nlattr *src, | 
 | 			   size_t size, int copy_len, int payload) | 
 | { | 
 | 	struct nlmsghdr *nlmsg = dst; | 
 | 	struct nlattr *nla; | 
 |  | 
 | 	/* xfrm_user_rcv_msg_compat() relies on fact that 32-bit messages | 
 | 	 * have the same len or shorted than 64-bit ones. | 
 | 	 * 32-bit translation that is bigger than 64-bit original is unexpected. | 
 | 	 */ | 
 | 	if (WARN_ON_ONCE(copy_len > payload)) | 
 | 		copy_len = payload; | 
 |  | 
 | 	if (size - *pos < nla_attr_size(payload)) | 
 | 		return -ENOBUFS; | 
 |  | 
 | 	nla = dst + *pos; | 
 |  | 
 | 	memcpy(nla, src, nla_attr_size(copy_len)); | 
 | 	nla->nla_len = nla_attr_size(payload); | 
 | 	*pos += nla_attr_size(copy_len); | 
 | 	nlmsg->nlmsg_len += nla->nla_len; | 
 |  | 
 | 	memset(dst + *pos, 0, payload - copy_len); | 
 | 	*pos += payload - copy_len; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int xfrm_xlate32_attr(void *dst, const struct nlattr *nla, | 
 | 			     size_t *pos, size_t size, | 
 | 			     struct netlink_ext_ack *extack) | 
 | { | 
 | 	int type = nla_type(nla); | 
 | 	u16 pol_len32, pol_len64; | 
 | 	int err; | 
 |  | 
 | 	if (type > XFRMA_MAX) { | 
 | 		BUILD_BUG_ON(XFRMA_MAX != XFRMA_IPTFS_PKT_SIZE); | 
 | 		NL_SET_ERR_MSG(extack, "Bad attribute"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 | 	type = array_index_nospec(type, XFRMA_MAX + 1); | 
 | 	if (nla_len(nla) < compat_policy[type].len) { | 
 | 		NL_SET_ERR_MSG(extack, "Attribute bad length"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 |  | 
 | 	pol_len32 = compat_policy[type].len; | 
 | 	pol_len64 = xfrma_policy[type].len; | 
 |  | 
 | 	/* XFRMA_SA and XFRMA_POLICY - need to know how-to translate */ | 
 | 	if (pol_len32 != pol_len64) { | 
 | 		if (nla_len(nla) != compat_policy[type].len) { | 
 | 			NL_SET_ERR_MSG(extack, "Attribute bad length"); | 
 | 			return -EOPNOTSUPP; | 
 | 		} | 
 | 		err = xfrm_attr_cpy32(dst, pos, nla, size, pol_len32, pol_len64); | 
 | 		if (err) | 
 | 			return err; | 
 | 	} | 
 |  | 
 | 	return xfrm_attr_cpy32(dst, pos, nla, size, nla_len(nla), nla_len(nla)); | 
 | } | 
 |  | 
 | static int xfrm_xlate32(struct nlmsghdr *dst, const struct nlmsghdr *src, | 
 | 			struct nlattr *attrs[XFRMA_MAX+1], | 
 | 			size_t size, u8 type, int maxtype, | 
 | 			struct netlink_ext_ack *extack) | 
 | { | 
 | 	size_t pos; | 
 | 	int i; | 
 |  | 
 | 	memcpy(dst, src, NLMSG_HDRLEN); | 
 | 	dst->nlmsg_len = NLMSG_HDRLEN + xfrm_msg_min[type]; | 
 | 	memset(nlmsg_data(dst), 0, xfrm_msg_min[type]); | 
 |  | 
 | 	switch (src->nlmsg_type) { | 
 | 	/* Compat message has the same layout as native */ | 
 | 	case XFRM_MSG_DELSA: | 
 | 	case XFRM_MSG_GETSA: | 
 | 	case XFRM_MSG_DELPOLICY: | 
 | 	case XFRM_MSG_GETPOLICY: | 
 | 	case XFRM_MSG_FLUSHSA: | 
 | 	case XFRM_MSG_FLUSHPOLICY: | 
 | 	case XFRM_MSG_NEWAE: | 
 | 	case XFRM_MSG_GETAE: | 
 | 	case XFRM_MSG_REPORT: | 
 | 	case XFRM_MSG_MIGRATE: | 
 | 	case XFRM_MSG_NEWSADINFO: | 
 | 	case XFRM_MSG_GETSADINFO: | 
 | 	case XFRM_MSG_NEWSPDINFO: | 
 | 	case XFRM_MSG_GETSPDINFO: | 
 | 	case XFRM_MSG_MAPPING: | 
 | 		memcpy(nlmsg_data(dst), nlmsg_data(src), compat_msg_min[type]); | 
 | 		break; | 
 | 	/* 4 byte alignment for trailing u64 on native, but not on compat */ | 
 | 	case XFRM_MSG_NEWSA: | 
 | 	case XFRM_MSG_NEWPOLICY: | 
 | 	case XFRM_MSG_UPDSA: | 
 | 	case XFRM_MSG_UPDPOLICY: | 
 | 		memcpy(nlmsg_data(dst), nlmsg_data(src), compat_msg_min[type]); | 
 | 		break; | 
 | 	case XFRM_MSG_EXPIRE: { | 
 | 		const struct compat_xfrm_user_expire *src_ue = nlmsg_data(src); | 
 | 		struct xfrm_user_expire *dst_ue = nlmsg_data(dst); | 
 |  | 
 | 		/* compat_xfrm_user_expire has 4-byte smaller state */ | 
 | 		memcpy(dst_ue, src_ue, sizeof(src_ue->state)); | 
 | 		dst_ue->hard = src_ue->hard; | 
 | 		break; | 
 | 	} | 
 | 	case XFRM_MSG_ACQUIRE: { | 
 | 		const struct compat_xfrm_user_acquire *src_ua = nlmsg_data(src); | 
 | 		struct xfrm_user_acquire *dst_ua = nlmsg_data(dst); | 
 |  | 
 | 		memcpy(dst_ua, src_ua, offsetof(struct compat_xfrm_user_acquire, aalgos)); | 
 | 		dst_ua->aalgos = src_ua->aalgos; | 
 | 		dst_ua->ealgos = src_ua->ealgos; | 
 | 		dst_ua->calgos = src_ua->calgos; | 
 | 		dst_ua->seq    = src_ua->seq; | 
 | 		break; | 
 | 	} | 
 | 	case XFRM_MSG_POLEXPIRE: { | 
 | 		const struct compat_xfrm_user_polexpire *src_upe = nlmsg_data(src); | 
 | 		struct xfrm_user_polexpire *dst_upe = nlmsg_data(dst); | 
 |  | 
 | 		/* compat_xfrm_user_polexpire has 4-byte smaller state */ | 
 | 		memcpy(dst_upe, src_upe, sizeof(src_upe->pol)); | 
 | 		dst_upe->hard = src_upe->hard; | 
 | 		break; | 
 | 	} | 
 | 	case XFRM_MSG_ALLOCSPI: { | 
 | 		const struct compat_xfrm_userspi_info *src_usi = nlmsg_data(src); | 
 | 		struct xfrm_userspi_info *dst_usi = nlmsg_data(dst); | 
 |  | 
 | 		/* compat_xfrm_user_polexpire has 4-byte smaller state */ | 
 | 		memcpy(dst_usi, src_usi, sizeof(src_usi->info)); | 
 | 		dst_usi->min = src_usi->min; | 
 | 		dst_usi->max = src_usi->max; | 
 | 		break; | 
 | 	} | 
 | 	default: | 
 | 		NL_SET_ERR_MSG(extack, "Unsupported message type"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 | 	pos = dst->nlmsg_len; | 
 |  | 
 | 	if (maxtype) { | 
 | 		/* attirbutes are xfrm_spdattr_type_t, not xfrm_attr_type_t */ | 
 | 		WARN_ON_ONCE(src->nlmsg_type != XFRM_MSG_NEWSPDINFO); | 
 |  | 
 | 		for (i = 1; i <= maxtype; i++) { | 
 | 			int err; | 
 |  | 
 | 			if (!attrs[i]) | 
 | 				continue; | 
 |  | 
 | 			/* just copy - no need for translation */ | 
 | 			err = xfrm_attr_cpy32(dst, &pos, attrs[i], size, | 
 | 					nla_len(attrs[i]), nla_len(attrs[i])); | 
 | 			if (err) | 
 | 				return err; | 
 | 		} | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	for (i = 1; i < XFRMA_MAX + 1; i++) { | 
 | 		int err; | 
 |  | 
 | 		if (i == XFRMA_PAD) | 
 | 			continue; | 
 |  | 
 | 		if (!attrs[i]) | 
 | 			continue; | 
 |  | 
 | 		err = xfrm_xlate32_attr(dst, attrs[i], &pos, size, extack); | 
 | 		if (err) | 
 | 			return err; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct nlmsghdr *xfrm_user_rcv_msg_compat(const struct nlmsghdr *h32, | 
 | 			int maxtype, const struct nla_policy *policy, | 
 | 			struct netlink_ext_ack *extack) | 
 | { | 
 | 	/* netlink_rcv_skb() checks if a message has full (struct nlmsghdr) */ | 
 | 	u16 type = h32->nlmsg_type - XFRM_MSG_BASE; | 
 | 	struct nlattr *attrs[XFRMA_MAX+1]; | 
 | 	struct nlmsghdr *h64; | 
 | 	size_t len; | 
 | 	int err; | 
 |  | 
 | 	BUILD_BUG_ON(ARRAY_SIZE(xfrm_msg_min) != ARRAY_SIZE(compat_msg_min)); | 
 |  | 
 | 	if (type >= ARRAY_SIZE(xfrm_msg_min)) | 
 | 		return ERR_PTR(-EINVAL); | 
 |  | 
 | 	/* Don't call parse: the message might have only nlmsg header */ | 
 | 	if ((h32->nlmsg_type == XFRM_MSG_GETSA || | 
 | 	     h32->nlmsg_type == XFRM_MSG_GETPOLICY) && | 
 | 	    (h32->nlmsg_flags & NLM_F_DUMP)) | 
 | 		return NULL; | 
 |  | 
 | 	err = nlmsg_parse_deprecated(h32, compat_msg_min[type], attrs, | 
 | 			maxtype ? : XFRMA_MAX, policy ? : compat_policy, extack); | 
 | 	if (err < 0) | 
 | 		return ERR_PTR(err); | 
 |  | 
 | 	len = xfrm_user_rcv_calculate_len64(h32, attrs, maxtype); | 
 | 	/* The message doesn't need translation */ | 
 | 	if (len == nlmsg_len(h32)) | 
 | 		return NULL; | 
 |  | 
 | 	len += NLMSG_HDRLEN; | 
 | 	h64 = kvmalloc(len, GFP_KERNEL); | 
 | 	if (!h64) | 
 | 		return ERR_PTR(-ENOMEM); | 
 |  | 
 | 	err = xfrm_xlate32(h64, h32, attrs, len, type, maxtype, extack); | 
 | 	if (err < 0) { | 
 | 		kvfree(h64); | 
 | 		return ERR_PTR(err); | 
 | 	} | 
 |  | 
 | 	return h64; | 
 | } | 
 |  | 
 | static int xfrm_user_policy_compat(u8 **pdata32, int optlen) | 
 | { | 
 | 	struct compat_xfrm_userpolicy_info *p = (void *)*pdata32; | 
 | 	u8 *src_templates, *dst_templates; | 
 | 	u8 *data64; | 
 |  | 
 | 	if (optlen < sizeof(*p)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	data64 = kmalloc_track_caller(optlen + 4, GFP_USER | __GFP_NOWARN); | 
 | 	if (!data64) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	memcpy(data64, *pdata32, sizeof(*p)); | 
 | 	memset(data64 + sizeof(*p), 0, 4); | 
 |  | 
 | 	src_templates = *pdata32 + sizeof(*p); | 
 | 	dst_templates = data64 + sizeof(*p) + 4; | 
 | 	memcpy(dst_templates, src_templates, optlen - sizeof(*p)); | 
 |  | 
 | 	kfree(*pdata32); | 
 | 	*pdata32 = data64; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct xfrm_translator xfrm_translator = { | 
 | 	.owner				= THIS_MODULE, | 
 | 	.alloc_compat			= xfrm_alloc_compat, | 
 | 	.rcv_msg_compat			= xfrm_user_rcv_msg_compat, | 
 | 	.xlate_user_policy_sockptr	= xfrm_user_policy_compat, | 
 | }; | 
 |  | 
 | static int __init xfrm_compat_init(void) | 
 | { | 
 | 	return xfrm_register_translator(&xfrm_translator); | 
 | } | 
 |  | 
 | static void __exit xfrm_compat_exit(void) | 
 | { | 
 | 	xfrm_unregister_translator(&xfrm_translator); | 
 | } | 
 |  | 
 | module_init(xfrm_compat_init); | 
 | module_exit(xfrm_compat_exit); | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_AUTHOR("Dmitry Safonov"); | 
 | MODULE_DESCRIPTION("XFRM 32-bit compatibility layer"); |