From: Johannes Berg <johannes.berg@xxxxxxxxx> Replace all the different nested versions of generic netlink backport with a single one, covering from < 3.13 all the way to the upcoming netlink policy improvements in 4.20. Signed-off-by: Johannes Berg <johannes.berg@xxxxxxxxx> --- backport/backport-include/linux/netlink.h | 2 +- backport/backport-include/net/genetlink.h | 238 ++++++----------- backport/compat/Makefile | 3 +- backport/compat/backport-3.13.c | 66 ----- backport/compat/backport-4.12.c | 274 -------------------- backport/compat/backport-genetlink.c | 418 ++++++++++++++++++++++++++++++ backport/compat/compat-3.3.c | 13 - 7 files changed, 502 insertions(+), 512 deletions(-) delete mode 100644 backport/compat/backport-4.12.c create mode 100644 backport/compat/backport-genetlink.c diff --git a/backport/backport-include/linux/netlink.h b/backport/backport-include/linux/netlink.h index 366c9e27e8c2..f956a76966f3 100644 --- a/backport/backport-include/linux/netlink.h +++ b/backport/backport-include/linux/netlink.h @@ -20,7 +20,7 @@ struct netlink_ext_ack { u8 cookie_len; /* backport only field */ - void *__bp_genl_real_ops; + void *__bp_doit; }; #define NL_SET_ERR_MSG(extack, msg) do { \ diff --git a/backport/backport-include/net/genetlink.h b/backport/backport-include/net/genetlink.h index d80d979ac423..84011e72644e 100644 --- a/backport/backport-include/net/genetlink.h +++ b/backport/backport-include/net/genetlink.h @@ -15,18 +15,14 @@ static inline void *__bp_genl_info_userhdr(struct genl_info *info) } #if LINUX_VERSION_IS_LESS(4,12,0) -#define GENL_SET_ERR_MSG(info, msg) do { } while (0) +#define GENL_SET_ERR_MSG(info, msg) NL_SET_ERR_MSG(genl_info_extack(info), msg) static inline int genl_err_attr(struct genl_info *info, int err, struct nlattr *attr) { -#if LINUX_VERSION_IS_GEQ(4,12,0) - info->extack->bad_attr = attr; -#endif - return err; } -#endif +#endif /* < 4.12 */ /* this is for patches we apply */ static inline struct netlink_ext_ack *genl_info_extack(struct genl_info *info) @@ -51,12 +47,24 @@ static inline void *genl_info_userhdr(struct genl_info *info) #define genl_info_snd_portid(__genl_info) (__genl_info->snd_portid) #endif +#if LINUX_VERSION_IS_LESS(3,13,0) +#define __genl_const +#else /* < 3.13 */ +#define __genl_const const +#endif /* < 3.13 */ + #ifndef GENLMSG_DEFAULT_SIZE #define GENLMSG_DEFAULT_SIZE (NLMSG_DEFAULT_SIZE - GENL_HDRLEN) #endif #if LINUX_VERSION_IS_LESS(3,1,0) -#define genl_dump_check_consistent(cb, user_hdr) +#define genl_dump_check_consistent(cb, user_hdr) do { } while (0) +#endif + +#if LINUX_VERSION_IS_LESS(4,10,0) +#define __genl_ro_after_init +#else +#define __genl_ro_after_init __ro_after_init #endif #if LINUX_VERSION_IS_LESS(4,15,0) @@ -83,148 +91,87 @@ void backport_genl_dump_check_consistent(struct netlink_callback *cb, #endif #endif /* LINUX_VERSION_IS_LESS(4,15,0) */ -#if LINUX_VERSION_IS_LESS(3,13,0) && RHEL_RELEASE_CODE < RHEL_RELEASE_VERSION(7,0) -static inline int __real_genl_register_family(struct genl_family *family) +#if LINUX_VERSION_IS_LESS(4,20,0) +static inline int +__real_backport_genl_register_family(struct genl_family *family) { return genl_register_family(family); } - -/* Needed for the mcgrps pointer */ -struct backport_genl_family { - struct genl_family family; - - unsigned int id, hdrsize, version, maxattr; - char name[GENL_NAMSIZ]; - bool netnsok; - bool parallel_ops; - - struct nlattr **attrbuf; - - int (*pre_doit)(struct genl_ops *ops, struct sk_buff *skb, - struct genl_info *info); - - void (*post_doit)(struct genl_ops *ops, struct sk_buff *skb, - struct genl_info *info); - - struct genl_multicast_group *mcgrps; - struct genl_ops *ops; - unsigned int n_mcgrps, n_ops; - - struct module *module; -}; -#define genl_family LINUX_BACKPORT(genl_family) - -int __backport_genl_register_family(struct genl_family *family); - -#define genl_register_family LINUX_BACKPORT(genl_register_family) static inline int -genl_register_family(struct genl_family *family) +__real_backport_genl_unregister_family(struct genl_family *family) { - family->module = THIS_MODULE; - return __backport_genl_register_family(family); + return genl_unregister_family(family); } -#define _genl_register_family_with_ops_grps \ - _backport_genl_register_family_with_ops_grps -static inline int -_genl_register_family_with_ops_grps(struct genl_family *family, - struct genl_ops *ops, size_t n_ops, - struct genl_multicast_group *mcgrps, - size_t n_mcgrps) -{ - family->ops = ops; - family->n_ops = n_ops; - family->mcgrps = mcgrps; - family->n_mcgrps = n_mcgrps; - return genl_register_family(family); -} +struct backport_genl_family { + struct genl_family family; + const struct genl_ops * copy_ops; + + /* copied */ + int id; /* private */ + unsigned int hdrsize; + char name[GENL_NAMSIZ]; + unsigned int version; + unsigned int maxattr; + bool netnsok; + bool parallel_ops; + int (*pre_doit)(__genl_const struct genl_ops *ops, + struct sk_buff *skb, + struct genl_info *info); + void (*post_doit)(__genl_const struct genl_ops *ops, + struct sk_buff *skb, + struct genl_info *info); +/* + * unsupported! + int (*mcast_bind)(struct net *net, int group); + void (*mcast_unbind)(struct net *net, int group); + */ + struct nlattr ** attrbuf; /* private */ + __genl_const struct genl_ops * ops; + __genl_const struct genl_multicast_group *mcgrps; + unsigned int n_ops; + unsigned int n_mcgrps; + struct module *module; +}; +#undef genl_family +#define genl_family backport_genl_family -#define genl_register_family_with_ops(family, ops) \ - _genl_register_family_with_ops_grps((family), \ - (ops), ARRAY_SIZE(ops), \ - NULL, 0) -#define genl_register_family_with_ops_groups(family, ops, grps) \ - _genl_register_family_with_ops_grps((family), \ - (ops), ARRAY_SIZE(ops), \ - (grps), ARRAY_SIZE(grps)) +#define genl_register_family backport_genl_register_family +int genl_register_family(struct genl_family *family); #define genl_unregister_family backport_genl_unregister_family -int genl_unregister_family(struct genl_family *family); +int backport_genl_unregister_family(struct genl_family *family); -#if LINUX_VERSION_IS_LESS(3,3,0) -extern void genl_notify(struct sk_buff *skb, struct net *net, u32 pid, - u32 group, struct nlmsghdr *nlh, gfp_t flags); -#endif -#define genl_notify(_fam, _skb, _info, _group, _flags) \ - genl_notify(_skb, genl_info_net(_info), \ - genl_info_snd_portid(_info), \ - (_fam)->mcgrps[_group].id, _info->nlhdr, _flags) -#define genlmsg_put(_skb, _pid, _seq, _fam, _flags, _cmd) \ - genlmsg_put(_skb, _pid, _seq, &(_fam)->family, _flags, _cmd) +#define genl_notify LINUX_BACKPORT(genl_notify) +void genl_notify(const struct genl_family *family, struct sk_buff *skb, + struct genl_info *info, u32 group, gfp_t flags); + +#define genlmsg_put LINUX_BACKPORT(genlmsg_put) +void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, + const struct genl_family *family, int flags, u8 cmd); + +#define genlmsg_put_reply LINUX_BACKPORT(genlmsg_put_reply) +void *genlmsg_put_reply(struct sk_buff *skb, + struct genl_info *info, + const struct genl_family *family, + int flags, u8 cmd); -#ifndef genlmsg_put_reply /* might already be there from _info override above */ -#define genlmsg_put_reply(_skb, _info, _fam, _flags, _cmd) \ - genlmsg_put_reply(_skb, _info, &(_fam)->family, _flags, _cmd) -#endif #define genlmsg_multicast_netns LINUX_BACKPORT(genlmsg_multicast_netns) -static inline int genlmsg_multicast_netns(struct genl_family *family, - struct net *net, struct sk_buff *skb, - u32 portid, unsigned int group, - gfp_t flags) -{ - if (WARN_ON_ONCE(group >= family->n_mcgrps)) - return -EINVAL; - group = family->mcgrps[group].id; - return nlmsg_multicast( - net->genl_sock, - skb, portid, group, flags); -} +int genlmsg_multicast_netns(const struct genl_family *family, + struct net *net, struct sk_buff *skb, + u32 portid, unsigned int group, + gfp_t flags); + #define genlmsg_multicast LINUX_BACKPORT(genlmsg_multicast) -static inline int genlmsg_multicast(struct genl_family *family, - struct sk_buff *skb, u32 portid, - unsigned int group, gfp_t flags) -{ - if (WARN_ON_ONCE(group >= family->n_mcgrps)) - return -EINVAL; - group = family->mcgrps[group].id; - return nlmsg_multicast( - init_net.genl_sock, - skb, portid, group, flags); -} -static inline int -backport_genlmsg_multicast_allns(struct genl_family *family, - struct sk_buff *skb, u32 portid, - unsigned int group, gfp_t flags) -{ - if (WARN_ON_ONCE(group >= family->n_mcgrps)) - return -EINVAL; - group = family->mcgrps[group].id; - return genlmsg_multicast_allns(skb, portid, group, flags); -} -#define genlmsg_multicast_allns LINUX_BACKPORT(genlmsg_multicast_allns) +int genlmsg_multicast(const struct genl_family *family, + struct sk_buff *skb, u32 portid, + unsigned int group, gfp_t flags); -#define __genl_const -#else /* < 3.13 */ -#define __genl_const const -#if LINUX_VERSION_IS_LESS(4,4,0) -#define genl_notify(_fam, _skb, _info, _group, _flags) \ - genl_notify(_fam, _skb, genl_info_net(_info), \ - genl_info_snd_portid(_info), \ - _group, _info->nlhdr, _flags) -#endif /* < 4.4 */ -#endif /* < 3.13 */ +#define genlmsg_multicast_allns LINUX_BACKPORT(genlmsg_multicast_allns) +int backport_genlmsg_multicast_allns(const struct genl_family *family, + struct sk_buff *skb, u32 portid, + unsigned int group, gfp_t flags); -#if LINUX_VERSION_IS_LESS(4,10,0) -/** - * genl_family_attrbuf - return family's attrbuf - * @family: the family - * - * Return the family's attrbuf, while validating that it's - * actually valid to access it. - * - * You cannot use this function with a family that has parallel_ops - * and you can only use it within (pre/post) doit/dumpit callbacks. - */ #define genl_family_attrbuf LINUX_BACKPORT(genl_family_attrbuf) static inline struct nlattr **genl_family_attrbuf(struct genl_family *family) { @@ -232,29 +179,6 @@ static inline struct nlattr **genl_family_attrbuf(struct genl_family *family) return family->attrbuf; } - -#define __genl_ro_after_init -#else -#define __genl_ro_after_init __ro_after_init -#endif - -#if LINUX_VERSION_IS_LESS(4,12,0) -static inline int -__real_bp_extack_genl_register_family(struct genl_family *family) -{ - return genl_register_family(family); -} -static inline int -__real_bp_extack_genl_unregister_family(struct genl_family *family) -{ - return genl_unregister_family(family); -} -int bp_extack_genl_register_family(struct genl_family *family); -int bp_extack_genl_unregister_family(struct genl_family *family); -#undef genl_register_family -#define genl_register_family bp_extack_genl_register_family -#undef genl_unregister_family -#define genl_unregister_family bp_extack_genl_unregister_family -#endif +#endif /* LINUX_VERSION_IS_LESS(4,20,0) */ #endif /* __BACKPORT_NET_GENETLINK_H */ diff --git a/backport/compat/Makefile b/backport/compat/Makefile index ff6c7e658380..fdc68c8b4600 100644 --- a/backport/compat/Makefile +++ b/backport/compat/Makefile @@ -36,10 +36,11 @@ compat-$(CPTCFG_KERNEL_4_6) += backport-4.6.o compat-$(CPTCFG_KERNEL_4_7) += backport-4.7.o compat-$(CPTCFG_KERNEL_4_8) += backport-4.8.o compat-$(CPTCFG_KERNEL_4_10) += backport-4.10.o -compat-$(CPTCFG_KERNEL_4_12) += backport-4.12.o compat-$(CPTCFG_KERNEL_4_18) += backport-4.18.o compat-$(CPTCFG_KERNEL_4_20) += backport-4.20.o +compat-$(CPTCFG_KERNEL_4_20) += backport-genetlink.o + compat-$(CPTCFG_BPAUTO_CRYPTO_SKCIPHER) += crypto-skcipher.o compat-$(CPTCFG_BPAUTO_BUILD_SYSTEM_DATA_VERIFICATION) += verification/verify.o diff --git a/backport/compat/backport-3.13.c b/backport/compat/backport-3.13.c index 9e5076fbcb9b..23c759ef2388 100644 --- a/backport/compat/backport-3.13.c +++ b/backport/compat/backport-3.13.c @@ -11,77 +11,11 @@ */ #include <linux/version.h> #include <linux/kernel.h> -#include <net/genetlink.h> #include <linux/delay.h> #include <linux/pci.h> #include <linux/device.h> #include <linux/hwmon.h> -/************* generic netlink backport *****************/ -#if RHEL_RELEASE_CODE < RHEL_RELEASE_VERSION(7,0) - -#undef genl_register_family -#undef genl_unregister_family - -int __backport_genl_register_family(struct genl_family *family) -{ - int i, ret; - -#define __copy(_field) family->family._field = family->_field - __copy(id); - __copy(hdrsize); - __copy(version); - __copy(maxattr); - strncpy(family->family.name, family->name, sizeof(family->family.name)); - __copy(netnsok); - __copy(pre_doit); - __copy(post_doit); -#if LINUX_VERSION_IS_GEQ(3,10,0) - __copy(parallel_ops); -#endif -#if LINUX_VERSION_IS_GEQ(3,11,0) - __copy(module); -#endif -#undef __copy - - ret = genl_register_family(&family->family); - if (ret < 0) - return ret; - - family->attrbuf = family->family.attrbuf; - family->id = family->family.id; - - for (i = 0; i < family->n_ops; i++) { - ret = genl_register_ops(&family->family, &family->ops[i]); - if (ret < 0) - goto error; - } - - for (i = 0; i < family->n_mcgrps; i++) { - ret = genl_register_mc_group(&family->family, - &family->mcgrps[i]); - if (ret) - goto error; - } - - return 0; - - error: - backport_genl_unregister_family(family); - return ret; -} -EXPORT_SYMBOL_GPL(__backport_genl_register_family); - -int backport_genl_unregister_family(struct genl_family *family) -{ - int err; - err = genl_unregister_family(&family->family); - return err; -} -EXPORT_SYMBOL_GPL(backport_genl_unregister_family); - -#endif /* RHEL_RELEASE_CODE < RHEL_RELEASE_VERSION(7,0) */ - #ifdef __BACKPORT_NET_GET_RANDOM_ONCE struct __net_random_once_work { struct work_struct work; diff --git a/backport/compat/backport-4.12.c b/backport/compat/backport-4.12.c deleted file mode 100644 index 5fc8c10cb8d8..000000000000 --- a/backport/compat/backport-4.12.c +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2017 Intel Deutschland GmbH - */ -#include <net/genetlink.h> -#include <net/sock.h> - -enum nlmsgerr_attrs { - NLMSGERR_ATTR_UNUSED, - NLMSGERR_ATTR_MSG, - NLMSGERR_ATTR_OFFS, - NLMSGERR_ATTR_COOKIE, - __NLMSGERR_ATTR_MAX, - NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1 -}; - -#define NLM_F_CAPPED 0x100 /* request was capped */ -#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */ - -struct bp_extack_genl_family { - struct genl_family family; - struct genl_family *real_family; - struct list_head list; - - struct genl_ops ops[]; -}; - -static LIST_HEAD(copies_list); -static DEFINE_MUTEX(copies_mutex); - -static const struct nla_policy extack_dummy_policy[1] = {}; - -static struct bp_extack_genl_family *get_copy(__genl_const struct genl_ops *op) -{ - do { - op--; - } while (op->policy != extack_dummy_policy); - - return container_of(op, struct bp_extack_genl_family, ops[0]); -} - -static int extack_pre_doit(__genl_const struct genl_ops *ops, - struct sk_buff *skb, - struct genl_info *info) -{ - struct netlink_ext_ack *extack = kzalloc(sizeof(*extack), GFP_KERNEL); - struct bp_extack_genl_family *copy = get_copy(ops); - struct genl_ops *real_ops; - int err; - - __bp_genl_info_userhdr_set(info, extack); - - if (!extack) { - __bp_genl_info_userhdr_set(info, ERR_PTR(-ENOMEM)); - return -ENOMEM; - } - - real_ops = (void *)©->real_family->ops[ops - ©->ops[1]]; - extack->__bp_genl_real_ops = real_ops; - - if (copy->real_family->pre_doit) - err = copy->real_family->pre_doit(real_ops, skb, info); - else - err = 0; - - if (err) { - __bp_genl_info_userhdr_set(info, ERR_PTR(err)); - kfree(extack); - } - - return err; -} - -static void extack_netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, - int err, const struct netlink_ext_ack *extack) -{ - struct sk_buff *skb; - struct nlmsghdr *rep; - struct nlmsgerr *errmsg; - size_t payload = sizeof(*errmsg); - size_t tlvlen = 0; - unsigned int flags = 0; - /* backports assumes everyone supports this - libnl does so it's true */ - bool nlk_has_extack = true; - - /* Error messages get the original request appened, unless the user - * requests to cap the error message, and get extra error data if - * requested. - * (ignored in backports) - */ - if (nlk_has_extack && extack && extack->_msg) - tlvlen += nla_total_size(strlen(extack->_msg) + 1); - - if (err) { - if (1) - payload += nlmsg_len(nlh); - else - flags |= NLM_F_CAPPED; - if (nlk_has_extack && extack && extack->bad_attr) - tlvlen += nla_total_size(sizeof(u32)); - } else { - flags |= NLM_F_CAPPED; - - if (nlk_has_extack && extack && extack->cookie_len) - tlvlen += nla_total_size(extack->cookie_len); - } - - if (tlvlen) - flags |= NLM_F_ACK_TLVS; - - skb = nlmsg_new(payload + tlvlen, GFP_KERNEL); - if (!skb) { - NETLINK_CB(in_skb).sk->sk_err = ENOBUFS; - NETLINK_CB(in_skb).sk->sk_error_report(NETLINK_CB(in_skb).sk); - return; - } - - rep = __nlmsg_put(skb, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, - NLMSG_ERROR, payload, flags); - errmsg = nlmsg_data(rep); - errmsg->error = err; - memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) ? nlh->nlmsg_len : sizeof(*nlh)); - - if (nlk_has_extack && extack) { - if (extack->_msg) { - WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG, - extack->_msg)); - } - if (err) { - if (extack->bad_attr && - !WARN_ON((u8 *)extack->bad_attr < in_skb->data || - (u8 *)extack->bad_attr >= in_skb->data + - in_skb->len)) - WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_OFFS, - (u8 *)extack->bad_attr - - in_skb->data)); - } else { - if (extack->cookie_len) - WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE, - extack->cookie_len, - extack->cookie)); - } - } - - nlmsg_end(skb, rep); - - netlink_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).portid, MSG_DONTWAIT); -} - -static int extack_doit(struct sk_buff *skb, struct genl_info *info) -{ - struct genl_ops *real_ops; - int err; - - /* older kernels have a bug here */ - if (IS_ERR(__bp_genl_info_userhdr(info))) { - extack_netlink_ack(skb, info->nlhdr, - PTR_ERR(__bp_genl_info_userhdr(info)), - genl_info_extack(info)); - goto out; - } - - real_ops = genl_info_extack(info)->__bp_genl_real_ops; - err = real_ops->doit(skb, info); - - if (err == -EINTR) - return err; - - if (info->nlhdr->nlmsg_flags & NLM_F_ACK || err) - extack_netlink_ack(skb, info->nlhdr, err, - genl_info_extack(info)); - -out: - /* suppress sending ACK from normal netlink code */ - info->nlhdr->nlmsg_flags &= ~NLM_F_ACK; - return 0; -} - -static void extack_post_doit(__genl_const struct genl_ops *ops, - struct sk_buff *skb, - struct genl_info *info) -{ - void (*post_doit)(__genl_const struct genl_ops *ops, - struct sk_buff *skb, - struct genl_info *info); - - post_doit = get_copy(ops)->real_family->post_doit; - - if (post_doit) - post_doit(ops, skb, info); - kfree(__bp_genl_info_userhdr(info)); -} - -int bp_extack_genl_register_family(struct genl_family *family) -{ - unsigned int size = sizeof(struct bp_extack_genl_family) + - sizeof(family->ops[0]) * (family->n_ops + 1); - struct bp_extack_genl_family *copy; - int i, err; - - copy = kzalloc(size, GFP_KERNEL); - if (!copy) - return -ENOMEM; - - copy->family = *family; - copy->real_family = family; - copy->family.ops = ©->ops[1]; - - for (i = 0; i < family->n_ops; i++) { - copy->ops[i + 1] = family->ops[i]; - if (family->ops[i].doit) - copy->ops[i + 1].doit = extack_doit; - } - - copy->ops[0].policy = extack_dummy_policy; - - copy->family.pre_doit = extack_pre_doit; - copy->family.post_doit = extack_post_doit; - - err = __real_bp_extack_genl_register_family(©->family); - if (err) { - kfree(copy); - return err; - } - - /* copy this since the family might access it directly */ - family->id = copy->family.id; - family->attrbuf = copy->family.attrbuf; - -#if LINUX_VERSION_IS_LESS(3,13,0) && RHEL_RELEASE_CODE < RHEL_RELEASE_VERSION(7,0) - /* family ID from the original family struct will be used when building - * genl messages (sent as nlmsg_type), so the new id should be updated - * in the original (older kernel format) family struct too - */ - family->family.id = copy->family.id; -#endif - -#if LINUX_VERSION_IS_GEQ(3,13,0) - family->mcgrp_offset = copy->family.mcgrp_offset; -#endif - - mutex_lock(&copies_mutex); - list_add_tail(©->list, &copies_list); - mutex_unlock(&copies_mutex); - - return 0; -} -EXPORT_SYMBOL_GPL(bp_extack_genl_register_family); - -int bp_extack_genl_unregister_family(struct genl_family *family) -{ - struct bp_extack_genl_family *tmp, *copy = NULL; - int err; - - mutex_lock(&copies_mutex); - list_for_each_entry(tmp, &copies_list, list) { - if (tmp->real_family == family) { - copy = tmp; - break; - } - } - if (copy) - list_del(©->list); - mutex_unlock(&copies_mutex); - - if (!copy) - return -ENOENT; - - err = __real_bp_extack_genl_unregister_family(©->family); - WARN_ON(err); - kfree(copy); - - return 0; -} -EXPORT_SYMBOL_GPL(bp_extack_genl_unregister_family); diff --git a/backport/compat/backport-genetlink.c b/backport/compat/backport-genetlink.c new file mode 100644 index 000000000000..c2579b353de7 --- /dev/null +++ b/backport/compat/backport-genetlink.c @@ -0,0 +1,418 @@ +/* + * Copyright 2017 Intel Deutschland GmbH + * Copyright (C) 2018 Intel Corporation + * + * Backport functionality introduced in Linux 4.20. + * Much of this is based on upstream lib/nlattr.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <net/genetlink.h> +#include <net/netlink.h> +#include <net/sock.h> + +static const struct genl_family *find_family_real_ops(const struct genl_ops **ops) +{ + const struct genl_family *family; + const struct genl_ops *tmp_ops = *ops; + + /* find the family ... */ + while (tmp_ops->doit || tmp_ops->dumpit) + tmp_ops++; + family = (void *)tmp_ops->done; + + /* cast to suppress const warning */ + *ops = (void *)(family->ops + (*ops - family->copy_ops)); + + return family; +} + +#if LINUX_VERSION_IS_LESS(4,12,0) +enum nlmsgerr_attrs { + NLMSGERR_ATTR_UNUSED, + NLMSGERR_ATTR_MSG, + NLMSGERR_ATTR_OFFS, + NLMSGERR_ATTR_COOKIE, + __NLMSGERR_ATTR_MAX, + NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1 +}; + +#define NLM_F_CAPPED 0x100 /* request was capped */ +#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */ + +static void extack_netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, + int err, const struct netlink_ext_ack *extack) +{ + struct sk_buff *skb; + struct nlmsghdr *rep; + struct nlmsgerr *errmsg; + size_t payload = sizeof(*errmsg); + size_t tlvlen = 0; + unsigned int flags = 0; + /* backports assumes everyone supports this - libnl does so it's true */ + bool nlk_has_extack = true; + + /* Error messages get the original request appened, unless the user + * requests to cap the error message, and get extra error data if + * requested. + * (ignored in backports) + */ + if (nlk_has_extack && extack && extack->_msg) + tlvlen += nla_total_size(strlen(extack->_msg) + 1); + + if (err) { + if (1) + payload += nlmsg_len(nlh); + else + flags |= NLM_F_CAPPED; + if (nlk_has_extack && extack && extack->bad_attr) + tlvlen += nla_total_size(sizeof(u32)); + } else { + flags |= NLM_F_CAPPED; + + if (nlk_has_extack && extack && extack->cookie_len) + tlvlen += nla_total_size(extack->cookie_len); + } + + if (tlvlen) + flags |= NLM_F_ACK_TLVS; + + skb = nlmsg_new(payload + tlvlen, GFP_KERNEL); + if (!skb) { + NETLINK_CB(in_skb).sk->sk_err = ENOBUFS; + NETLINK_CB(in_skb).sk->sk_error_report(NETLINK_CB(in_skb).sk); + return; + } + + rep = __nlmsg_put(skb, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, + NLMSG_ERROR, payload, flags); + errmsg = nlmsg_data(rep); + errmsg->error = err; + memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) ? nlh->nlmsg_len : sizeof(*nlh)); + + if (nlk_has_extack && extack) { + if (extack->_msg) { + WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG, + extack->_msg)); + } + if (err) { + if (extack->bad_attr && + !WARN_ON((u8 *)extack->bad_attr < in_skb->data || + (u8 *)extack->bad_attr >= in_skb->data + + in_skb->len)) + WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_OFFS, + (u8 *)extack->bad_attr - + in_skb->data)); + } else { + if (extack->cookie_len) + WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE, + extack->cookie_len, + extack->cookie)); + } + } + + nlmsg_end(skb, rep); + + netlink_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).portid, MSG_DONTWAIT); +} + +static int extack_doit(struct sk_buff *skb, struct genl_info *info) +{ + int (*doit)(struct sk_buff *, struct genl_info *); + int err; + + /* older kernels have a bug here */ + if (IS_ERR(__bp_genl_info_userhdr(info))) { + extack_netlink_ack(skb, info->nlhdr, + PTR_ERR(__bp_genl_info_userhdr(info)), + genl_info_extack(info)); + goto out; + } + + doit = genl_info_extack(info)->__bp_doit; + err = doit(skb, info); + + if (err == -EINTR) + return err; + + if (info->nlhdr->nlmsg_flags & NLM_F_ACK || err) + extack_netlink_ack(skb, info->nlhdr, err, + genl_info_extack(info)); + +out: + /* suppress sending ACK from normal netlink code */ + info->nlhdr->nlmsg_flags &= ~NLM_F_ACK; + return 0; +} +#endif /* LINUX_VERSION_IS_LESS(4,12,0) */ + +static int backport_pre_doit(__genl_const struct genl_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + const struct genl_family *family = find_family_real_ops(&ops); + int err; +#if LINUX_VERSION_IS_LESS(4,12,0) + struct netlink_ext_ack *extack = kzalloc(sizeof(*extack), GFP_KERNEL); + + __bp_genl_info_userhdr_set(info, extack); + + if (!extack) { + __bp_genl_info_userhdr_set(info, ERR_PTR(-ENOMEM)); + return -ENOMEM; + } + + extack->__bp_doit = ops->doit; +#else + struct netlink_ext_ack *extack = info->extack; +#endif + + err = nlmsg_validate(info->nlhdr, GENL_HDRLEN + family->hdrsize, + family->maxattr, ops->policy, extack); + if (err) + return err; + + err = family->pre_doit(ops, skb, info); + +#if LINUX_VERSION_IS_LESS(4,12,0) + if (err) { + __bp_genl_info_userhdr_set(info, ERR_PTR(err)); + kfree(extack); + } +#endif + + return err; +} + +static void backport_post_doit(__genl_const struct genl_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + const struct genl_family *family = find_family_real_ops(&ops); + + family->post_doit(ops, skb, info); + + kfree(__bp_genl_info_userhdr(info)); +} + +int backport_genl_register_family(struct genl_family *family) +{ + struct genl_ops *ops; + int err, i; + +#define COPY(memb) family->family.memb = family->memb +#define BACK(memb) family->memb = family->family.memb + + /* we append one entry to the ops to find our family pointer ... */ + ops = kzalloc(sizeof(*ops) * (family->n_ops + 1), GFP_KERNEL); + memcpy(ops, family->ops, sizeof(*ops) * family->n_ops); + /* + * Remove policy to skip validation as the struct nla_policy + * memory layout isn't compatible with the old version + */ + for (i = 0; i < family->n_ops; i++) { + ops[i].policy = NULL; +#if LINUX_VERSION_IS_LESS(4,12,0) + if (ops[i].doit) + ops[i].doit = extack_doit; +#endif + } + /* keep doit/dumpit NULL - that's invalid */ + ops[family->n_ops].done = (void *)family; + + COPY(id); + memcpy(family->family.name, family->name, sizeof(family->name)); + COPY(hdrsize); + COPY(version); + COPY(maxattr); + COPY(netnsok); +#if LINUX_VERSION_IS_GEQ(3,10,0) + COPY(parallel_ops); +#endif + family->family.pre_doit = backport_pre_doit; + family->family.post_doit = backport_post_doit; + /* attrbuf is output only */ + family->copy_ops = ops; +#if LINUX_VERSION_IS_GEQ(3,13,0) + family->family.ops = ops; + COPY(mcgrps); + COPY(n_ops); + COPY(n_mcgrps); +#endif +#if LINUX_VERSION_IS_GEQ(3,11,0) + COPY(module); +#endif + + err = __real_backport_genl_register_family(&family->family); + + BACK(id); + BACK(attrbuf); + + if (err) + return err; + +#if LINUX_VERSION_IS_GEQ(3,13,0) || RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(7,0) + return 0; +#else + for (i = 0; i < family->n_ops; i++) { + err = genl_register_ops(&family->family, &family->ops[i]); + if (err < 0) + goto error; + } + + for (i = 0; i < family->n_mcgrps; i++) { + err = genl_register_mc_group(&family->family, + &family->mcgrps[i]); + if (err) + goto error; + } + + return 0; + error: + genl_unregister_family(family); + return err; +#endif /* LINUX_VERSION_IS_GEQ(3,13,0) || RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(7,0) */ +} +EXPORT_SYMBOL_GPL(backport_genl_register_family); + +int backport_genl_unregister_family(struct genl_family *family) +{ + return __real_backport_genl_unregister_family(&family->family); +} +EXPORT_SYMBOL_GPL(backport_genl_unregister_family); + +#define INVALID_GROUP 0xffffffff + +static u32 __backport_genl_group(const struct genl_family *family, + u32 group) +{ + if (WARN_ON_ONCE(group >= family->n_mcgrps)) + return INVALID_GROUP; +#if LINUX_VERSION_IS_LESS(3,13,0) + return family->mcgrps[group].id; +#else + return family->family.mcgrp_offset + group; +#endif +} + +void genl_notify(const struct genl_family *family, struct sk_buff *skb, + struct genl_info *info, u32 group, gfp_t flags) +{ + struct net *net = genl_info_net(info); + struct sock *sk = net->genl_sock; + int report = 0; + + if (info->nlhdr) + report = nlmsg_report(info->nlhdr); + + group = __backport_genl_group(family, group); + if (group == INVALID_GROUP) + return; + nlmsg_notify(sk, skb, info->snd_portid, group, report, flags); +} +EXPORT_SYMBOL_GPL(genl_notify); + +void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, + const struct genl_family *family, int flags, u8 cmd) +{ + struct nlmsghdr *nlh; + struct genlmsghdr *hdr; + + nlh = nlmsg_put(skb, portid, seq, family->id, GENL_HDRLEN + + family->hdrsize, flags); + if (nlh == NULL) + return NULL; + + hdr = nlmsg_data(nlh); + hdr->cmd = cmd; + hdr->version = family->version; + hdr->reserved = 0; + + return (char *) hdr + GENL_HDRLEN; +} +EXPORT_SYMBOL_GPL(genlmsg_put); + +void *genlmsg_put_reply(struct sk_buff *skb, + struct genl_info *info, + const struct genl_family *family, + int flags, u8 cmd) +{ + return genlmsg_put(skb, info->snd_portid, info->snd_seq, family, + flags, cmd); +} +EXPORT_SYMBOL_GPL(genlmsg_put_reply); + +int genlmsg_multicast_netns(const struct genl_family *family, + struct net *net, struct sk_buff *skb, + u32 portid, unsigned int group, + gfp_t flags) +{ + group = __backport_genl_group(family, group); + if (group == INVALID_GROUP) + return -EINVAL; + return nlmsg_multicast(net->genl_sock, skb, portid, group, flags); +} +EXPORT_SYMBOL_GPL(genlmsg_multicast_netns); + +int genlmsg_multicast(const struct genl_family *family, + struct sk_buff *skb, u32 portid, + unsigned int group, gfp_t flags) +{ + return genlmsg_multicast_netns(family, &init_net, skb, + portid, group, flags); +} +EXPORT_SYMBOL_GPL(genlmsg_multicast); + +static int genlmsg_mcast(struct sk_buff *skb, u32 portid, unsigned long group, + gfp_t flags) +{ + struct sk_buff *tmp; + struct net *net, *prev = NULL; + bool delivered = false; + int err; + + for_each_net_rcu(net) { + if (prev) { + tmp = skb_clone(skb, flags); + if (!tmp) { + err = -ENOMEM; + goto error; + } + err = nlmsg_multicast(prev->genl_sock, tmp, + portid, group, flags); + if (!err) + delivered = true; + else if (err != -ESRCH) + goto error; + } + + prev = net; + } + + err = nlmsg_multicast(prev->genl_sock, skb, portid, group, flags); + if (!err) + delivered = true; + else if (err != -ESRCH) + return err; + return delivered ? 0 : -ESRCH; + error: + kfree_skb(skb); + return err; +} + +int backport_genlmsg_multicast_allns(const struct genl_family *family, + struct sk_buff *skb, u32 portid, + unsigned int group, gfp_t flags) +{ + group = __backport_genl_group(family, group); + if (group == INVALID_GROUP) + return -EINVAL; + return genlmsg_mcast(skb, portid, group, flags); +} +EXPORT_SYMBOL_GPL(backport_genlmsg_multicast_allns); diff --git a/backport/compat/compat-3.3.c b/backport/compat/compat-3.3.c index c064f09d9abe..1185a5d2d4e0 100644 --- a/backport/compat/compat-3.3.c +++ b/backport/compat/compat-3.3.c @@ -224,16 +224,3 @@ void backport_destroy_workqueue(struct workqueue_struct *wq) spin_unlock(&wq_name_lock); } EXPORT_SYMBOL_GPL(backport_destroy_workqueue); - -void genl_notify(struct sk_buff *skb, struct net *net, u32 pid, u32 group, - struct nlmsghdr *nlh, gfp_t flags) -{ - struct sock *sk = net->genl_sock; - int report = 0; - - if (nlh) - report = nlmsg_report(nlh); - - nlmsg_notify(sk, skb, pid, group, report, flags); -} -EXPORT_SYMBOL_GPL(genl_notify); -- 2.14.4 -- To unsubscribe from this list: send the line "unsubscribe backports" in