diff --git a/include/linux/hookers.h b/include/linux/hookers.h new file mode 100644 index 0000000000000000000000000000000000000000..c5f7e62c8ee263c9fa04bc0cdd55d48c83392781 --- /dev/null +++ b/include/linux/hookers.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 Alibaba Group Holding Limited. All Rights Reserved. + * + * Changes: Li Yu + */ + +#ifndef _LINUX_HOOKER_H_ +#define _LINUX_HOOKER_H_ + +#include + +struct hooked_place; + +struct hooker { + struct hooked_place *hplace; + void *func; /* the installed hooker function pointer */ + struct list_head chain; +}; + +/* + * Install the hooker function at specified address. + * This function may sleep. + * + * Parameters: + * place - the address that saves function pointer + * hooker - the hooker to install, the caller must fill + * its func member first + * + * Return: + * 0 - All OK, please note that hooker func may be called before + * this return + * < 0 - any error, e.g. out of memory, existing same installed hooker + */ + +extern int hooker_install(const void *place, struct hooker *hooker); + +/* + * Remove the installed hooker function that saved in hooker->func. + * This function may sleep. + * + * Parameters: + * place - the address that saves function pointer + * hooker - the installed hooker struct + */ + +extern void hooker_uninstall(struct hooker *hooker); + +#endif diff --git a/include/net/sock.h b/include/net/sock.h index 4545a9ecc219356a5c5bfea765a6f4cb16ef6644..33661448b2b4c501503244dfa08a0667b15384c0 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -323,6 +323,7 @@ struct sock_common { * @sk_clockid: clockid used by time-based scheduling (SO_TXTIME) * @sk_txtime_deadline_mode: set deadline mode for SO_TXTIME * @sk_txtime_unused: unused txtime flags + * @sk_toa_data: tcp option address (toa) data */ struct sock { /* @@ -508,6 +509,7 @@ struct sock { #endif void (*sk_destruct)(struct sock *sk); struct sock_reuseport __rcu *sk_reuseport_cb; + __be32 sk_toa_data[16]; struct rcu_head sk_rcu; }; diff --git a/include/net/transp_v6.h b/include/net/transp_v6.h index a8f6020f1196edc9940cbb6c605a06279db4fd36..4da02f9c54e11003b5ceb01e0885d502b846a3ff 100644 --- a/include/net/transp_v6.h +++ b/include/net/transp_v6.h @@ -58,6 +58,8 @@ ip6_dgram_sock_seq_show(struct seq_file *seq, struct sock *sp, __u16 srcp, /* address family specific functions */ extern const struct inet_connection_sock_af_ops ipv4_specific; +extern const struct inet_connection_sock_af_ops ipv6_specific; +extern const struct inet_connection_sock_af_ops ipv6_mapped; void inet6_destroy_sock(struct sock *sk); diff --git a/net/Kconfig b/net/Kconfig index 228dfa382eeca8afd12d32475978259738dceb53..747be0af9fe4b561ad27d100678e81561bed9d6a 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -60,6 +60,7 @@ source "net/xfrm/Kconfig" source "net/iucv/Kconfig" source "net/smc/Kconfig" source "net/xdp/Kconfig" +source "net/hookers/Kconfig" config INET bool "TCP/IP networking" diff --git a/net/Makefile b/net/Makefile index bdaf53925acd5606fdb953800620bd05cf0f259e..474dead20351b0e2561e6890bb86dc23a7d8a795 100644 --- a/net/Makefile +++ b/net/Makefile @@ -87,3 +87,4 @@ endif obj-$(CONFIG_QRTR) += qrtr/ obj-$(CONFIG_NET_NCSI) += ncsi/ obj-$(CONFIG_XDP_SOCKETS) += xdp/ +obj-$(CONFIG_HOOKERS) += hookers/ diff --git a/net/hookers/Kconfig b/net/hookers/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..bec94cc8d865ef8ec956c7a471d538be0a1716cd --- /dev/null +++ b/net/hookers/Kconfig @@ -0,0 +1,8 @@ +config HOOKERS + tristate "Hooker service" + default m + ---help--- + Allow replacing and restore the function pointer in any order. + See include/linux/hookers.h for details. + + Say m if unsure. \ No newline at end of file diff --git a/net/hookers/Makefile b/net/hookers/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b03b42400763cb464b2153aaeaf4dfe93e796b43 --- /dev/null +++ b/net/hookers/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for hookers module. +# +obj-$(CONFIG_HOOKERS) += hookers.o diff --git a/net/hookers/hookers.c b/net/hookers/hookers.c new file mode 100644 index 0000000000000000000000000000000000000000..c753789d568c2950656dfeb90c8e3739f39581a6 --- /dev/null +++ b/net/hookers/hookers.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2019 Alibaba Group Holding Limited. All Rights Reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct hooked_place { + const char *name; /* position information shown in procfs */ + void *place; /* the kernel address to be hook */ + void *orig; /* original content at hooked place */ + void *stub; /* hooker function stub */ + int nr_hookers; /* how many hookers are linked at below chain */ + struct list_head chain; /* hookers chain */ +}; + +static spinlock_t hookers_lock; + +static struct sock * +ipv4_specific_syn_recv_sock_stub(struct sock *sk, + struct sk_buff *skb, struct request_sock *req, + struct dst_entry *dst, + struct request_sock *req_unhash, + bool *own_req); +static struct sock * +ipv6_specific_syn_recv_sock_stub(struct sock *sk, + struct sk_buff *skb, struct request_sock *req, + struct dst_entry *dst, + struct request_sock *req_unhash, + bool *own_req); +static struct sock * +ipv6_mapped_syn_recv_sock_stub(struct sock *sk, + struct sk_buff *skb, struct request_sock *req, + struct dst_entry *dst, + struct request_sock *req_unhash, + bool *own_req); +static int +inet_stream_ops_getname_stub(struct socket *sock, + struct sockaddr *uaddr, int peer); +static int +inet6_stream_ops_getname_stub(struct socket *sock, + struct sockaddr *uaddr, int peer); + +static struct hooked_place place_table[] = { + { + .name = "ipv4_specific.syn_recv_sock", + .place = (void *)&ipv4_specific.syn_recv_sock, + .stub = ipv4_specific_syn_recv_sock_stub, + }, + + { + .name = "ipv6_specific.syn_recv_sock", + .place = (void *)&ipv6_specific.syn_recv_sock, + .stub = ipv6_specific_syn_recv_sock_stub, + }, + + { + .name = "ipv6_mapped.syn_recv_sock", + .place = (void *)&ipv6_mapped.syn_recv_sock, + .stub = ipv6_mapped_syn_recv_sock_stub, + }, + + { + .name = "inet_stream_ops.getname", + .place = (void *)&inet_stream_ops.getname, + .stub = inet_stream_ops_getname_stub, + }, + + { + .name = "inet6_stream_ops.getname", + .place = (void *)&inet6_stream_ops.getname, + .stub = inet6_stream_ops_getname_stub, + }, +}; + +static struct sock * +__syn_recv_sock_hstub(struct hooked_place *place, + struct sock *sk, struct sk_buff *skb, + struct request_sock *req, struct dst_entry *dst, + struct request_sock *req_unhash, bool *own_req) +{ + struct hooker *iter; + struct sock *(*hooker_func)(struct sock *sk, struct sk_buff *skb, + struct request_sock *req, + struct dst_entry *dst, + struct request_sock *req_unhash, + bool *own_req, + struct sock **ret); + struct sock *(*orig_func)(struct sock *sk, struct sk_buff *skb, + struct request_sock *req, + struct dst_entry *dst, + struct request_sock *req_unhash, + bool *own_req); + struct sock *ret; + + orig_func = place->orig; + ret = orig_func(sk, skb, req, dst, req_unhash, own_req); + + rcu_read_lock(); + list_for_each_entry_rcu(iter, &place->chain, chain) { + hooker_func = iter->func; + hooker_func(sk, skb, req, dst, req_unhash, own_req, &ret); + } + rcu_read_unlock(); + + return ret; +} + +static int __getname_hstub(struct hooked_place *place, + struct socket *sock, struct sockaddr *uaddr, + int peer) +{ + struct hooker *iter; + int (*hooker_func)(struct socket *sock, struct sockaddr *uaddr, + int peer, int *ret); + int (*orig_func)(struct socket *sock, struct sockaddr *uaddr, + int peer); + int ret; + + orig_func = place->orig; + ret = orig_func(sock, uaddr, peer); + + rcu_read_lock(); + list_for_each_entry_rcu(iter, &place->chain, chain) { + hooker_func = iter->func; + hooker_func(sock, uaddr, peer, &ret); + } + rcu_read_unlock(); + + return ret; +} + +static struct sock * +ipv4_specific_syn_recv_sock_stub(struct sock *sk, + struct sk_buff *skb, struct request_sock *req, + struct dst_entry *dst, + struct request_sock *req_unhash, + bool *own_req) +{ + return __syn_recv_sock_hstub(&place_table[0], sk, skb, req, dst, + req_unhash, own_req); +} + +static struct sock * +ipv6_specific_syn_recv_sock_stub(struct sock *sk, + struct sk_buff *skb, struct request_sock *req, + struct dst_entry *dst, + struct request_sock *req_unhash, + bool *own_req) +{ + return __syn_recv_sock_hstub(&place_table[1], sk, skb, req, dst, + req_unhash, own_req); +} + +static struct sock * +ipv6_mapped_syn_recv_sock_stub(struct sock *sk, + struct sk_buff *skb, struct request_sock *req, + struct dst_entry *dst, + struct request_sock *req_unhash, + bool *own_req) +{ + return __syn_recv_sock_hstub(&place_table[2], sk, skb, req, dst, + req_unhash, own_req); +} + +static int +inet_stream_ops_getname_stub(struct socket *sock, + struct sockaddr *uaddr, int peer) +{ + return __getname_hstub(&place_table[3], sock, uaddr, peer); +} + +static int +inet6_stream_ops_getname_stub(struct socket *sock, + struct sockaddr *uaddr, int peer) +{ + return __getname_hstub(&place_table[4], sock, uaddr, peer); +} + +#define PLACE_TABLE_SZ (sizeof((place_table)) / sizeof((place_table)[0])) + +int hooker_install(const void *place, struct hooker *h) +{ + int i; + struct hooked_place *hplace; + + /* synchronize_rcu() */ + might_sleep(); + + if (!place || !h || !h->func) + return -EINVAL; + + for (i = 0; i < PLACE_TABLE_SZ; i++) { + hplace = &place_table[i]; + if (hplace->place == place) { + INIT_LIST_HEAD(&h->chain); + spin_lock(&hookers_lock); + hplace->nr_hookers++; + h->hplace = hplace; + list_add_tail_rcu(&h->chain, &place_table[i].chain); + spin_unlock(&hookers_lock); + synchronize_rcu(); + break; + } + } + + return (i >= PLACE_TABLE_SZ) ? -EINVAL : 0; +} +EXPORT_SYMBOL_GPL(hooker_install); + +void hooker_uninstall(struct hooker *h) +{ + /* synchronize_rcu(); */ + might_sleep(); + + spin_lock(&hookers_lock); + list_del_rcu(&h->chain); + h->hplace->nr_hookers--; + h->hplace = NULL; + spin_unlock(&hookers_lock); + synchronize_rcu(); +} +EXPORT_SYMBOL_GPL(hooker_uninstall); + +static inline unsigned int hookers_clear_cr0(void) +{ + unsigned int cr0 = read_cr0(); + + write_cr0(cr0 & 0xfffeffff); + return cr0; +} + +static inline void hookers_restore_cr0(unsigned int val) +{ + write_cr0(val); +} + +static void *hookers_seq_start(struct seq_file *seq, loff_t *pos) +{ + if (*pos < PLACE_TABLE_SZ) + return &place_table[*pos]; + return NULL; +} + +static void *hookers_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + if (++(*pos) >= PLACE_TABLE_SZ) + return NULL; + + return (void *)&place_table[*pos]; +} + +static void hookers_seq_stop(struct seq_file *seq, void *v) +{ +} + +static int hookers_seq_show(struct seq_file *seq, void *v) +{ + struct hooked_place *hplace = (struct hooked_place *)v; + + seq_printf(seq, "name:%-24s addr:0x%p hookers:%-10d\n", + hplace->name, hplace->place, hplace->nr_hookers); + return 0; +} + +static const struct seq_operations hookers_seq_ops = { + .start = hookers_seq_start, + .next = hookers_seq_next, + .stop = hookers_seq_stop, + .show = hookers_seq_show, +}; + +static int hookers_seq_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &hookers_seq_ops); +} + +static const struct file_operations hookers_seq_fops = { + .owner = THIS_MODULE, + .open = hookers_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int hookers_init(void) +{ + int i; + + if (!proc_create("hookers", 0444, NULL, &hookers_seq_fops)) + return -ENODEV; + + spin_lock_init(&hookers_lock); + for (i = 0; i < PLACE_TABLE_SZ; i++) { + unsigned int cr0; + void **place = place_table[i].place; + + place_table[i].orig = *place; + if (!place_table[i].stub) + break; + INIT_LIST_HEAD(&place_table[i].chain); + get_online_cpus(); + cr0 = hookers_clear_cr0(); + *place = place_table[i].stub; + hookers_restore_cr0(cr0); + put_online_cpus(); + } + + return 0; +} + +static void hookers_exit(void) +{ + int i; + + remove_proc_entry("hookers", NULL); + + for (i = 0; i < PLACE_TABLE_SZ; i++) { + unsigned int cr0; + void **place = place_table[i].place; + + get_online_cpus(); + cr0 = hookers_clear_cr0(); + *place = place_table[i].orig; + hookers_restore_cr0(cr0); + put_online_cpus(); + } + synchronize_rcu(); +} + +module_init(hookers_init); +module_exit(hookers_exit); +MODULE_LICENSE("GPL"); diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index 79fcd9550fd2e83d9cdedb8b17da60895c0a9e55..c7dff4a46bbcfd259446a08a1d581bc832ea952a 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -606,6 +606,7 @@ const struct proto_ops inet6_stream_ops = { #endif .set_rcvlowat = tcp_set_rcvlowat, }; +EXPORT_SYMBOL(inet6_stream_ops); const struct proto_ops inet6_dgram_ops = { .family = PF_INET6, diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index 9a117a79af659ce043498dbbe7a0c231df5f14a5..091cef7fa30cc5fa0f05f553cd57f4cf34dde5b9 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -77,8 +77,8 @@ static void tcp_v6_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb); -static const struct inet_connection_sock_af_ops ipv6_mapped; -static const struct inet_connection_sock_af_ops ipv6_specific; +const struct inet_connection_sock_af_ops ipv6_mapped; +const struct inet_connection_sock_af_ops ipv6_specific; #ifdef CONFIG_TCP_MD5SIG static const struct tcp_sock_af_ops tcp_sock_ipv6_specific; static const struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific; @@ -1682,7 +1682,7 @@ static struct timewait_sock_ops tcp6_timewait_sock_ops = { .twsk_destructor = tcp_twsk_destructor, }; -static const struct inet_connection_sock_af_ops ipv6_specific = { +const struct inet_connection_sock_af_ops ipv6_specific = { .queue_xmit = inet6_csk_xmit, .send_check = tcp_v6_send_check, .rebuild_header = inet6_sk_rebuild_header, @@ -1701,6 +1701,7 @@ static const struct inet_connection_sock_af_ops ipv6_specific = { #endif .mtu_reduced = tcp_v6_mtu_reduced, }; +EXPORT_SYMBOL(ipv6_specific); #ifdef CONFIG_TCP_MD5SIG static const struct tcp_sock_af_ops tcp_sock_ipv6_specific = { @@ -1713,7 +1714,7 @@ static const struct tcp_sock_af_ops tcp_sock_ipv6_specific = { /* * TCP over IPv4 via INET6 API */ -static const struct inet_connection_sock_af_ops ipv6_mapped = { +const struct inet_connection_sock_af_ops ipv6_mapped = { .queue_xmit = ip_queue_xmit, .send_check = tcp_v4_send_check, .rebuild_header = inet_sk_rebuild_header, @@ -1731,6 +1732,7 @@ static const struct inet_connection_sock_af_ops ipv6_mapped = { #endif .mtu_reduced = tcp_v4_mtu_reduced, }; +EXPORT_SYMBOL(ipv6_mapped); #ifdef CONFIG_TCP_MD5SIG static const struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific = {