From f9dc3713df1229aa8168169e9cf1010fbac68de5 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 13 Jul 2020 15:01:33 -0700 Subject: [PATCH 1/3] usb: gadget: udc: Avoid tasklet passing a global There's no reason for the tasklet callback to set an argument since it always uses a global. Instead, use the global directly, in preparation for converting the tasklet subsystem to modern callback conventions. Reviewed-by: Greg Kroah-Hartman Acked-by: Thomas Gleixner Signed-off-by: Kees Cook --- drivers/usb/gadget/udc/snps_udc_core.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c index 3fcded31405a..afdd28f332ce 100644 --- a/drivers/usb/gadget/udc/snps_udc_core.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -96,9 +96,7 @@ static int stop_pollstall_timer; static DECLARE_COMPLETION(on_pollstall_exit); /* tasklet for usb disconnect */ -static DECLARE_TASKLET(disconnect_tasklet, udc_tasklet_disconnect, - (unsigned long) &udc); - +static DECLARE_TASKLET(disconnect_tasklet, udc_tasklet_disconnect, 0); /* endpoint names used for print */ static const char ep0_string[] = "ep0in"; @@ -1661,7 +1659,7 @@ static void usb_disconnect(struct udc *dev) /* Tasklet for disconnect to be outside of interrupt context */ static void udc_tasklet_disconnect(unsigned long par) { - struct udc *dev = (struct udc *)(*((struct udc **) par)); + struct udc *dev = udc; u32 tmp; DBG(dev, "Tasklet disconnect\n"); From b13fecb1c3a603c4b8e99b306fecf4f668c11b32 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 13 Jul 2020 15:01:26 -0700 Subject: [PATCH 2/3] treewide: Replace DECLARE_TASKLET() with DECLARE_TASKLET_OLD() This converts all the existing DECLARE_TASKLET() (and ...DISABLED) macros with DECLARE_TASKLET_OLD() in preparation for refactoring the tasklet callback type. All existing DECLARE_TASKLET() users had a "0" data argument, it has been removed here as well. Reviewed-by: Greg Kroah-Hartman Acked-by: Thomas Gleixner Signed-off-by: Kees Cook --- drivers/input/keyboard/omap-keypad.c | 2 +- drivers/input/serio/hil_mlc.c | 2 +- drivers/net/wan/farsync.c | 4 ++-- drivers/s390/crypto/ap_bus.c | 2 +- drivers/staging/most/dim2/dim2.c | 2 +- drivers/staging/octeon/ethernet-tx.c | 2 +- drivers/tty/vt/keyboard.c | 2 +- drivers/usb/gadget/udc/snps_udc_core.c | 2 +- drivers/usb/host/fhci-sched.c | 2 +- include/linux/interrupt.h | 15 ++++++++++----- kernel/backtracetest.c | 2 +- kernel/debug/debug_core.c | 2 +- kernel/irq/resend.c | 2 +- net/atm/pppoatm.c | 2 +- net/iucv/iucv.c | 2 +- sound/drivers/pcsp/pcsp_lib.c | 2 +- 16 files changed, 26 insertions(+), 21 deletions(-) diff --git a/drivers/input/keyboard/omap-keypad.c b/drivers/input/keyboard/omap-keypad.c index 5fe7a5633e33..dbe836c7ff47 100644 --- a/drivers/input/keyboard/omap-keypad.c +++ b/drivers/input/keyboard/omap-keypad.c @@ -46,7 +46,7 @@ struct omap_kp { unsigned short keymap[]; }; -static DECLARE_TASKLET_DISABLED(kp_tasklet, omap_kp_tasklet, 0); +static DECLARE_TASKLET_DISABLED_OLD(kp_tasklet, omap_kp_tasklet); static unsigned int *row_gpios; static unsigned int *col_gpios; diff --git a/drivers/input/serio/hil_mlc.c b/drivers/input/serio/hil_mlc.c index e1423f7648d6..65f4e9d62a67 100644 --- a/drivers/input/serio/hil_mlc.c +++ b/drivers/input/serio/hil_mlc.c @@ -77,7 +77,7 @@ static struct timer_list hil_mlcs_kicker; static int hil_mlcs_probe; static void hil_mlcs_process(unsigned long unused); -static DECLARE_TASKLET_DISABLED(hil_mlcs_tasklet, hil_mlcs_process, 0); +static DECLARE_TASKLET_DISABLED_OLD(hil_mlcs_tasklet, hil_mlcs_process); /* #define HIL_MLC_DEBUG */ diff --git a/drivers/net/wan/farsync.c b/drivers/net/wan/farsync.c index 7916efce7188..f5198a391417 100644 --- a/drivers/net/wan/farsync.c +++ b/drivers/net/wan/farsync.c @@ -569,8 +569,8 @@ static void do_bottom_half_rx(struct fst_card_info *card); static void fst_process_tx_work_q(unsigned long work_q); static void fst_process_int_work_q(unsigned long work_q); -static DECLARE_TASKLET(fst_tx_task, fst_process_tx_work_q, 0); -static DECLARE_TASKLET(fst_int_task, fst_process_int_work_q, 0); +static DECLARE_TASKLET_OLD(fst_tx_task, fst_process_tx_work_q); +static DECLARE_TASKLET_OLD(fst_int_task, fst_process_int_work_q); static struct fst_card_info *fst_card_array[FST_MAX_CARDS]; static spinlock_t fst_work_q_lock; diff --git a/drivers/s390/crypto/ap_bus.c b/drivers/s390/crypto/ap_bus.c index e71ca4a719a5..2589ccd257e3 100644 --- a/drivers/s390/crypto/ap_bus.c +++ b/drivers/s390/crypto/ap_bus.c @@ -93,7 +93,7 @@ static DECLARE_WORK(ap_scan_work, ap_scan_bus); * Tasklet & timer for AP request polling and interrupts */ static void ap_tasklet_fn(unsigned long); -static DECLARE_TASKLET(ap_tasklet, ap_tasklet_fn, 0); +static DECLARE_TASKLET_OLD(ap_tasklet, ap_tasklet_fn); static DECLARE_WAIT_QUEUE_HEAD(ap_poll_wait); static struct task_struct *ap_poll_kthread; static DEFINE_MUTEX(ap_poll_thread_mutex); diff --git a/drivers/staging/most/dim2/dim2.c b/drivers/staging/most/dim2/dim2.c index 8e0f27e61652..509c8012d20b 100644 --- a/drivers/staging/most/dim2/dim2.c +++ b/drivers/staging/most/dim2/dim2.c @@ -46,7 +46,7 @@ MODULE_PARM_DESC(fcnt, "Num of frames per sub-buffer for sync channels as a powe static DEFINE_SPINLOCK(dim_lock); static void dim2_tasklet_fn(unsigned long data); -static DECLARE_TASKLET(dim2_tasklet, dim2_tasklet_fn, 0); +static DECLARE_TASKLET_OLD(dim2_tasklet, dim2_tasklet_fn); /** * struct hdm_channel - private structure to keep channel specific data diff --git a/drivers/staging/octeon/ethernet-tx.c b/drivers/staging/octeon/ethernet-tx.c index ab7dd8216006..9c71ad5af7b9 100644 --- a/drivers/staging/octeon/ethernet-tx.c +++ b/drivers/staging/octeon/ethernet-tx.c @@ -41,7 +41,7 @@ #endif static void cvm_oct_tx_do_cleanup(unsigned long arg); -static DECLARE_TASKLET(cvm_oct_tx_cleanup_tasklet, cvm_oct_tx_do_cleanup, 0); +static DECLARE_TASKLET_OLD(cvm_oct_tx_cleanup_tasklet, cvm_oct_tx_do_cleanup); /* Maximum number of SKBs to try to free per xmit packet. */ #define MAX_SKB_TO_FREE (MAX_OUT_QUEUE_DEPTH * 2) diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index 568b2171f335..f80199984ee0 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -1236,7 +1236,7 @@ static void kbd_bh(unsigned long dummy) } } -DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0); +DECLARE_TASKLET_DISABLED_OLD(keyboard_tasklet, kbd_bh); #if defined(CONFIG_X86) || defined(CONFIG_IA64) || defined(CONFIG_ALPHA) ||\ defined(CONFIG_MIPS) || defined(CONFIG_PPC) || defined(CONFIG_SPARC) ||\ diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c index afdd28f332ce..e76f1a50b0fc 100644 --- a/drivers/usb/gadget/udc/snps_udc_core.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -96,7 +96,7 @@ static int stop_pollstall_timer; static DECLARE_COMPLETION(on_pollstall_exit); /* tasklet for usb disconnect */ -static DECLARE_TASKLET(disconnect_tasklet, udc_tasklet_disconnect, 0); +static DECLARE_TASKLET_OLD(disconnect_tasklet, udc_tasklet_disconnect); /* endpoint names used for print */ static const char ep0_string[] = "ep0in"; diff --git a/drivers/usb/host/fhci-sched.c b/drivers/usb/host/fhci-sched.c index 3235d5307403..5c423f240a1f 100644 --- a/drivers/usb/host/fhci-sched.c +++ b/drivers/usb/host/fhci-sched.c @@ -677,7 +677,7 @@ static void process_done_list(unsigned long data) enable_irq(fhci_to_hcd(fhci)->irq); } -DECLARE_TASKLET(fhci_tasklet, process_done_list, 0); +DECLARE_TASKLET_OLD(fhci_tasklet, process_done_list); /* transfer complted callback */ u32 fhci_transfer_confirm_callback(struct fhci_hcd *fhci) diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h index 5db970b6615a..b911196f03eb 100644 --- a/include/linux/interrupt.h +++ b/include/linux/interrupt.h @@ -612,12 +612,17 @@ struct tasklet_struct unsigned long data; }; -#define DECLARE_TASKLET(name, func, data) \ -struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data } - -#define DECLARE_TASKLET_DISABLED(name, func, data) \ -struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data } +#define DECLARE_TASKLET_OLD(name, _func) \ +struct tasklet_struct name = { \ + .count = ATOMIC_INIT(0), \ + .func = _func, \ +} +#define DECLARE_TASKLET_DISABLED_OLD(name, _func) \ +struct tasklet_struct name = { \ + .count = ATOMIC_INIT(1), \ + .func = _func, \ +} enum { diff --git a/kernel/backtracetest.c b/kernel/backtracetest.c index a2a97fa3071b..370217dd7e39 100644 --- a/kernel/backtracetest.c +++ b/kernel/backtracetest.c @@ -29,7 +29,7 @@ static void backtrace_test_irq_callback(unsigned long data) complete(&backtrace_work); } -static DECLARE_TASKLET(backtrace_tasklet, &backtrace_test_irq_callback, 0); +static DECLARE_TASKLET_OLD(backtrace_tasklet, &backtrace_test_irq_callback); static void backtrace_test_irq(void) { diff --git a/kernel/debug/debug_core.c b/kernel/debug/debug_core.c index 9e5934780f41..b16dbc1bf056 100644 --- a/kernel/debug/debug_core.c +++ b/kernel/debug/debug_core.c @@ -1068,7 +1068,7 @@ static void kgdb_tasklet_bpt(unsigned long ing) atomic_set(&kgdb_break_tasklet_var, 0); } -static DECLARE_TASKLET(kgdb_tasklet_breakpoint, kgdb_tasklet_bpt, 0); +static DECLARE_TASKLET_OLD(kgdb_tasklet_breakpoint, kgdb_tasklet_bpt); void kgdb_schedule_breakpoint(void) { diff --git a/kernel/irq/resend.c b/kernel/irq/resend.c index 27634f4022d0..c48ce19a257f 100644 --- a/kernel/irq/resend.c +++ b/kernel/irq/resend.c @@ -45,7 +45,7 @@ static void resend_irqs(unsigned long arg) } /* Tasklet to handle resend: */ -static DECLARE_TASKLET(resend_tasklet, resend_irqs, 0); +static DECLARE_TASKLET_OLD(resend_tasklet, resend_irqs); static int irq_sw_resend(struct irq_desc *desc) { diff --git a/net/atm/pppoatm.c b/net/atm/pppoatm.c index 45d8e1d5d033..579b66da1d95 100644 --- a/net/atm/pppoatm.c +++ b/net/atm/pppoatm.c @@ -393,7 +393,7 @@ static int pppoatm_assign_vcc(struct atm_vcc *atmvcc, void __user *arg) * Each PPPoATM instance has its own tasklet - this is just a * prototypical one used to initialize them */ - static const DECLARE_TASKLET(tasklet_proto, pppoatm_wakeup_sender, 0); + static const DECLARE_TASKLET_OLD(tasklet_proto, pppoatm_wakeup_sender); if (copy_from_user(&be, arg, sizeof be)) return -EFAULT; if (be.encaps != PPPOATM_ENCAPS_AUTODETECT && diff --git a/net/iucv/iucv.c b/net/iucv/iucv.c index 19250a0c85d3..cd2e468852e7 100644 --- a/net/iucv/iucv.c +++ b/net/iucv/iucv.c @@ -105,7 +105,7 @@ static LIST_HEAD(iucv_task_queue); * The tasklet for fast delivery of iucv interrupts. */ static void iucv_tasklet_fn(unsigned long); -static DECLARE_TASKLET(iucv_tasklet, iucv_tasklet_fn,0); +static DECLARE_TASKLET_OLD(iucv_tasklet, iucv_tasklet_fn); /* * Queue of interrupt buffers for delivery via a work queue diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c index 05244b11ed5e..4e79293d7f11 100644 --- a/sound/drivers/pcsp/pcsp_lib.c +++ b/sound/drivers/pcsp/pcsp_lib.c @@ -36,7 +36,7 @@ static void pcsp_call_pcm_elapsed(unsigned long priv) } } -static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0); +static DECLARE_TASKLET_OLD(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed); /* write the port and returns the next expire time in ns; * called at the trigger-start and in hrtimer callback From 12cc923f1ccc1df467e046b02a72c2b3b321b6a2 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Sun, 29 Sep 2019 18:30:13 +0200 Subject: [PATCH 3/3] tasklet: Introduce new initialization API Nowadays, modern kernel subsystems that use callbacks pass the data structure associated with a given callback as argument to the callback. The tasklet subsystem remains one which passes an arbitrary unsigned long to the callback function. This has several problems: - This keeps an extra field for storing the argument in each tasklet data structure, it bloats the tasklet_struct structure with a redundant .data field - No type checking can be performed on this argument. Instead of using container_of() like other callback subsystems, it forces callbacks to do explicit type cast of the unsigned long argument into the required object type. - Buffer overflows can overwrite the .func and the .data field, so an attacker can easily overwrite the function and its first argument to whatever it wants. Add a new tasklet initialization API, via DECLARE_TASKLET() and tasklet_setup(), which will replace the existing ones. This work is greatly inspired by the timer_struct conversion series, see commit e99e88a9d2b0 ("treewide: setup_timer() -> timer_setup()") To avoid problems with both -Wcast-function-type (which is enabled in the kernel via -Wextra is several subsystems), and with mismatched function prototypes when build with Control Flow Integrity enabled, this adds the "use_callback" member to let the tasklet caller choose which union member to call through. Once all old API uses are removed, this and the .data member will be removed as well. (On 64-bit this does not grow the struct size as the new member fills the hole after atomic_t, which is also "int" sized.) Signed-off-by: Romain Perier Co-developed-by: Allen Pais Signed-off-by: Allen Pais Reviewed-by: Greg Kroah-Hartman Acked-by: Thomas Gleixner Co-developed-by: Kees Cook Signed-off-by: Kees Cook --- include/linux/interrupt.h | 28 +++++++++++++++++++++++++++- kernel/softirq.c | 18 +++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h index b911196f03eb..f9aee3538461 100644 --- a/include/linux/interrupt.h +++ b/include/linux/interrupt.h @@ -585,6 +585,9 @@ static inline struct task_struct *this_cpu_ksoftirqd(void) /* Tasklets --- multithreaded analogue of BHs. + This API is deprecated. Please consider using threaded IRQs instead: + https://lore.kernel.org/lkml/20200716081538.2sivhkj4hcyrusem@linutronix.de + Main feature differing them of generic softirqs: tasklet is running only on one CPU simultaneously. @@ -608,10 +611,31 @@ struct tasklet_struct struct tasklet_struct *next; unsigned long state; atomic_t count; - void (*func)(unsigned long); + bool use_callback; + union { + void (*func)(unsigned long data); + void (*callback)(struct tasklet_struct *t); + }; unsigned long data; }; +#define DECLARE_TASKLET(name, _callback) \ +struct tasklet_struct name = { \ + .count = ATOMIC_INIT(0), \ + .callback = _callback, \ + .use_callback = true, \ +} + +#define DECLARE_TASKLET_DISABLED(name, _callback) \ +struct tasklet_struct name = { \ + .count = ATOMIC_INIT(1), \ + .callback = _callback, \ + .use_callback = true, \ +} + +#define from_tasklet(var, callback_tasklet, tasklet_fieldname) \ + container_of(callback_tasklet, typeof(*var), tasklet_fieldname) + #define DECLARE_TASKLET_OLD(name, _func) \ struct tasklet_struct name = { \ .count = ATOMIC_INIT(0), \ @@ -691,6 +715,8 @@ extern void tasklet_kill(struct tasklet_struct *t); extern void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu); extern void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); +extern void tasklet_setup(struct tasklet_struct *t, + void (*callback)(struct tasklet_struct *)); /* * Autoprobing for irqs: diff --git a/kernel/softirq.c b/kernel/softirq.c index c4201b7f42b1..292e7c2d2333 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -547,7 +547,10 @@ static void tasklet_action_common(struct softirq_action *a, if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) BUG(); - t->func(t->data); + if (t->use_callback) + t->callback(t); + else + t->func(t->data); tasklet_unlock(t); continue; } @@ -573,6 +576,18 @@ static __latent_entropy void tasklet_hi_action(struct softirq_action *a) tasklet_action_common(a, this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ); } +void tasklet_setup(struct tasklet_struct *t, + void (*callback)(struct tasklet_struct *)) +{ + t->next = NULL; + t->state = 0; + atomic_set(&t->count, 0); + t->callback = callback; + t->use_callback = true; + t->data = 0; +} +EXPORT_SYMBOL(tasklet_setup); + void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data) { @@ -580,6 +595,7 @@ void tasklet_init(struct tasklet_struct *t, t->state = 0; atomic_set(&t->count, 0); t->func = func; + t->use_callback = false; t->data = data; } EXPORT_SYMBOL(tasklet_init);