summaryrefslogtreecommitdiffstats
path: root/drivers/net/wan/hdlc.c
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/net/wan/hdlc.c
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/net/wan/hdlc.c')
-rw-r--r--drivers/net/wan/hdlc.c379
1 files changed, 379 insertions, 0 deletions
diff --git a/drivers/net/wan/hdlc.c b/drivers/net/wan/hdlc.c
new file mode 100644
index 000000000..cbed10b1d
--- /dev/null
+++ b/drivers/net/wan/hdlc.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Generic HDLC support routines for Linux
+ *
+ * Copyright (C) 1999 - 2008 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * Currently supported:
+ * * raw IP-in-HDLC
+ * * Cisco HDLC
+ * * Frame Relay with ANSI or CCITT LMI (both user and network side)
+ * * PPP
+ * * X.25
+ *
+ * Use sethdlc utility to set line parameters, protocol and PVCs
+ *
+ * How does it work:
+ * - proto->open(), close(), start(), stop() calls are serialized.
+ * The order is: open, [ start, stop ... ] close ...
+ * - proto->start() and stop() are called with spin_lock_irq held.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/hdlc.h>
+#include <linux/if_arp.h>
+#include <linux/inetdevice.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/pkt_sched.h>
+#include <linux/poll.h>
+#include <linux/rtnetlink.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <net/net_namespace.h>
+
+static const char *version = "HDLC support module revision 1.22";
+
+#undef DEBUG_LINK
+
+static struct hdlc_proto *first_proto;
+
+static int hdlc_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *p, struct net_device *orig_dev)
+{
+ struct hdlc_device *hdlc;
+
+ /* First make sure "dev" is an HDLC device */
+ if (!(dev->priv_flags & IFF_WAN_HDLC)) {
+ kfree_skb(skb);
+ return NET_RX_SUCCESS;
+ }
+
+ hdlc = dev_to_hdlc(dev);
+
+ if (!net_eq(dev_net(dev), &init_net)) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ BUG_ON(!hdlc->proto->netif_rx);
+ return hdlc->proto->netif_rx(skb);
+}
+
+netdev_tx_t hdlc_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+
+ if (hdlc->proto->xmit)
+ return hdlc->proto->xmit(skb, dev);
+
+ return hdlc->xmit(skb, dev); /* call hardware driver directly */
+}
+EXPORT_SYMBOL(hdlc_start_xmit);
+
+static inline void hdlc_proto_start(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+
+ if (hdlc->proto->start)
+ hdlc->proto->start(dev);
+}
+
+static inline void hdlc_proto_stop(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+
+ if (hdlc->proto->stop)
+ hdlc->proto->stop(dev);
+}
+
+static int hdlc_device_event(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ hdlc_device *hdlc;
+ unsigned long flags;
+ int on;
+
+ if (!net_eq(dev_net(dev), &init_net))
+ return NOTIFY_DONE;
+
+ if (!(dev->priv_flags & IFF_WAN_HDLC))
+ return NOTIFY_DONE; /* not an HDLC device */
+
+ if (event != NETDEV_CHANGE)
+ return NOTIFY_DONE; /* Only interested in carrier changes */
+
+ on = netif_carrier_ok(dev);
+
+#ifdef DEBUG_LINK
+ printk(KERN_DEBUG "%s: hdlc_device_event NETDEV_CHANGE, carrier %i\n",
+ dev->name, on);
+#endif
+
+ hdlc = dev_to_hdlc(dev);
+ spin_lock_irqsave(&hdlc->state_lock, flags);
+
+ if (hdlc->carrier == on)
+ goto carrier_exit; /* no change in DCD line level */
+
+ hdlc->carrier = on;
+
+ if (!hdlc->open)
+ goto carrier_exit;
+
+ if (hdlc->carrier) {
+ netdev_info(dev, "Carrier detected\n");
+ hdlc_proto_start(dev);
+ } else {
+ netdev_info(dev, "Carrier lost\n");
+ hdlc_proto_stop(dev);
+ }
+
+carrier_exit:
+ spin_unlock_irqrestore(&hdlc->state_lock, flags);
+ return NOTIFY_DONE;
+}
+
+/* Must be called by hardware driver when HDLC device is being opened */
+int hdlc_open(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+ printk(KERN_DEBUG "%s: hdlc_open() carrier %i open %i\n", dev->name,
+ hdlc->carrier, hdlc->open);
+#endif
+
+ if (!hdlc->proto)
+ return -ENOSYS; /* no protocol attached */
+
+ if (hdlc->proto->open) {
+ int result = hdlc->proto->open(dev);
+
+ if (result)
+ return result;
+ }
+
+ spin_lock_irq(&hdlc->state_lock);
+
+ if (hdlc->carrier) {
+ netdev_info(dev, "Carrier detected\n");
+ hdlc_proto_start(dev);
+ } else {
+ netdev_info(dev, "No carrier\n");
+ }
+
+ hdlc->open = 1;
+
+ spin_unlock_irq(&hdlc->state_lock);
+ return 0;
+}
+EXPORT_SYMBOL(hdlc_open);
+
+/* Must be called by hardware driver when HDLC device is being closed */
+void hdlc_close(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+ printk(KERN_DEBUG "%s: hdlc_close() carrier %i open %i\n", dev->name,
+ hdlc->carrier, hdlc->open);
+#endif
+
+ spin_lock_irq(&hdlc->state_lock);
+
+ hdlc->open = 0;
+ if (hdlc->carrier)
+ hdlc_proto_stop(dev);
+
+ spin_unlock_irq(&hdlc->state_lock);
+
+ if (hdlc->proto->close)
+ hdlc->proto->close(dev);
+}
+EXPORT_SYMBOL(hdlc_close);
+
+int hdlc_ioctl(struct net_device *dev, struct if_settings *ifs)
+{
+ struct hdlc_proto *proto = first_proto;
+ int result;
+
+ if (dev_to_hdlc(dev)->proto) {
+ result = dev_to_hdlc(dev)->proto->ioctl(dev, ifs);
+ if (result != -EINVAL)
+ return result;
+ }
+
+ /* Not handled by currently attached protocol (if any) */
+
+ while (proto) {
+ result = proto->ioctl(dev, ifs);
+ if (result != -EINVAL)
+ return result;
+ proto = proto->next;
+ }
+ return -EINVAL;
+}
+EXPORT_SYMBOL(hdlc_ioctl);
+
+static const struct header_ops hdlc_null_ops;
+
+static void hdlc_setup_dev(struct net_device *dev)
+{
+ /* Re-init all variables changed by HDLC protocol drivers,
+ * including ether_setup() called from hdlc_raw_eth.c.
+ */
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ dev->priv_flags = IFF_WAN_HDLC;
+ dev->mtu = HDLC_MAX_MTU;
+ dev->min_mtu = 68;
+ dev->max_mtu = HDLC_MAX_MTU;
+ dev->type = ARPHRD_RAWHDLC;
+ dev->hard_header_len = 0;
+ dev->needed_headroom = 0;
+ dev->addr_len = 0;
+ dev->header_ops = &hdlc_null_ops;
+}
+
+static void hdlc_setup(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+
+ hdlc_setup_dev(dev);
+ hdlc->carrier = 1;
+ hdlc->open = 0;
+ spin_lock_init(&hdlc->state_lock);
+}
+
+struct net_device *alloc_hdlcdev(void *priv)
+{
+ struct net_device *dev;
+
+ dev = alloc_netdev(sizeof(struct hdlc_device), "hdlc%d",
+ NET_NAME_UNKNOWN, hdlc_setup);
+ if (dev)
+ dev_to_hdlc(dev)->priv = priv;
+ return dev;
+}
+EXPORT_SYMBOL(alloc_hdlcdev);
+
+void unregister_hdlc_device(struct net_device *dev)
+{
+ rtnl_lock();
+ detach_hdlc_protocol(dev);
+ unregister_netdevice(dev);
+ rtnl_unlock();
+}
+EXPORT_SYMBOL(unregister_hdlc_device);
+
+int attach_hdlc_protocol(struct net_device *dev, struct hdlc_proto *proto,
+ size_t size)
+{
+ int err;
+
+ err = detach_hdlc_protocol(dev);
+ if (err)
+ return err;
+
+ if (!try_module_get(proto->module))
+ return -ENOSYS;
+
+ if (size) {
+ dev_to_hdlc(dev)->state = kmalloc(size, GFP_KERNEL);
+ if (!dev_to_hdlc(dev)->state) {
+ module_put(proto->module);
+ return -ENOBUFS;
+ }
+ }
+ dev_to_hdlc(dev)->proto = proto;
+
+ return 0;
+}
+EXPORT_SYMBOL(attach_hdlc_protocol);
+
+int detach_hdlc_protocol(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ int err;
+
+ if (hdlc->proto) {
+ err = call_netdevice_notifiers(NETDEV_PRE_TYPE_CHANGE, dev);
+ err = notifier_to_errno(err);
+ if (err) {
+ netdev_err(dev, "Refused to change device type\n");
+ return err;
+ }
+
+ if (hdlc->proto->detach)
+ hdlc->proto->detach(dev);
+ module_put(hdlc->proto->module);
+ hdlc->proto = NULL;
+ }
+ kfree(hdlc->state);
+ hdlc->state = NULL;
+ hdlc_setup_dev(dev);
+
+ return 0;
+}
+EXPORT_SYMBOL(detach_hdlc_protocol);
+
+void register_hdlc_protocol(struct hdlc_proto *proto)
+{
+ rtnl_lock();
+ proto->next = first_proto;
+ first_proto = proto;
+ rtnl_unlock();
+}
+EXPORT_SYMBOL(register_hdlc_protocol);
+
+void unregister_hdlc_protocol(struct hdlc_proto *proto)
+{
+ struct hdlc_proto **p;
+
+ rtnl_lock();
+ p = &first_proto;
+ while (*p != proto) {
+ BUG_ON(!*p);
+ p = &((*p)->next);
+ }
+ *p = proto->next;
+ rtnl_unlock();
+}
+EXPORT_SYMBOL(unregister_hdlc_protocol);
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("HDLC support module");
+MODULE_LICENSE("GPL v2");
+
+static struct packet_type hdlc_packet_type __read_mostly = {
+ .type = cpu_to_be16(ETH_P_HDLC),
+ .func = hdlc_rcv,
+};
+
+static struct notifier_block hdlc_notifier = {
+ .notifier_call = hdlc_device_event,
+};
+
+static int __init hdlc_module_init(void)
+{
+ int result;
+
+ pr_info("%s\n", version);
+ result = register_netdevice_notifier(&hdlc_notifier);
+ if (result)
+ return result;
+ dev_add_pack(&hdlc_packet_type);
+ return 0;
+}
+
+static void __exit hdlc_module_exit(void)
+{
+ dev_remove_pack(&hdlc_packet_type);
+ unregister_netdevice_notifier(&hdlc_notifier);
+}
+
+module_init(hdlc_module_init);
+module_exit(hdlc_module_exit);