| /* Adapted from linux 5.3.11: drivers/net/wireless/ath/ath10k/usb.c |
| Reduced reproducer for CVE-2019-19078 (leak of struct urb). */ |
| |
| typedef unsigned char u8; |
| typedef unsigned short u16; |
| typedef _Bool bool; |
| |
| #define ENOMEM 12 |
| #define EINVAL 22 |
| |
| /* The original file has this licence header. */ |
| |
| // SPDX-License-Identifier: ISC |
| /* |
| * Copyright (c) 2007-2011 Atheros Communications Inc. |
| * Copyright (c) 2011-2012,2017 Qualcomm Atheros, Inc. |
| * Copyright (c) 2016-2017 Erik Stromdahl <erik.stromdahl@gmail.com> |
| */ |
| |
| /* Adapted from include/linux/compiler_attributes.h. */ |
| #define __aligned(x) __attribute__((__aligned__(x))) |
| #define __printf(a, b) __attribute__((__format__(printf, a, b))) |
| |
| /* Possible macro for the new attribute. */ |
| #define __malloc(f) __attribute__((malloc(f))); |
| |
| /* From include/linux/types.h. */ |
| |
| typedef unsigned int gfp_t; |
| |
| /* Not the real value, which is in include/linux/gfp.h. */ |
| #define GFP_ATOMIC 32 |
| |
| /* From include/linux/usb.h. */ |
| |
| struct urb; |
| extern void usb_free_urb(struct urb *urb); |
| extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags) |
| __malloc(usb_free_urb); |
| /* attribute added as part of testcase */ |
| |
| extern int usb_submit_urb(/*struct urb *urb, */gfp_t mem_flags); |
| extern void usb_unanchor_urb(struct urb *urb); |
| |
| /* From drivers/net/wireless/ath/ath10k/core.h. */ |
| |
| struct ath10k; |
| |
| struct ath10k { |
| /* [...many other fields removed...] */ |
| |
| /* must be last */ |
| u8 drv_priv[0] __aligned(sizeof(void *)); |
| }; |
| |
| /* From drivers/net/wireless/ath/ath10k/debug.h. */ |
| |
| enum ath10k_debug_mask { |
| /* [...other values removed...] */ |
| ATH10K_DBG_USB_BULK = 0x00080000, |
| }; |
| |
| extern unsigned int ath10k_debug_mask; |
| |
| __printf(3, 4) void __ath10k_dbg(struct ath10k *ar, |
| enum ath10k_debug_mask mask, |
| const char *fmt, ...); |
| |
| /* Simplified for now, to avoid pulling in tracepoint code. */ |
| static inline |
| bool trace_ath10k_log_dbg_enabled(void) { return 0; } |
| |
| #define ath10k_dbg(ar, dbg_mask, fmt, ...) \ |
| do { \ |
| if ((ath10k_debug_mask & dbg_mask) || \ |
| trace_ath10k_log_dbg_enabled()) \ |
| __ath10k_dbg(ar, dbg_mask, fmt, ##__VA_ARGS__); \ |
| } while (0) |
| |
| /* From drivers/net/wireless/ath/ath10k/hif.h. */ |
| |
| struct ath10k_hif_sg_item { |
| /* [...other fields removed...] */ |
| void *transfer_context; /* NULL = tx completion callback not called */ |
| }; |
| |
| struct ath10k_hif_ops { |
| /* send a scatter-gather list to the target */ |
| int (*tx_sg)(struct ath10k *ar, u8 pipe_id, |
| struct ath10k_hif_sg_item *items, int n_items); |
| /* [...other fields removed...] */ |
| }; |
| |
| /* From drivers/net/wireless/ath/ath10k/usb.h. */ |
| |
| /* tx/rx pipes for usb */ |
| enum ath10k_usb_pipe_id { |
| /* [...other values removed...] */ |
| ATH10K_USB_PIPE_MAX = 8 |
| }; |
| |
| struct ath10k_usb_pipe { |
| /* [...all fields removed...] */ |
| }; |
| |
| /* usb device object */ |
| struct ath10k_usb { |
| /* [...other fields removed...] */ |
| struct ath10k_usb_pipe pipes[ATH10K_USB_PIPE_MAX]; |
| }; |
| |
| /* usb urb object */ |
| struct ath10k_urb_context { |
| /* [...other fields removed...] */ |
| struct ath10k_usb_pipe *pipe; |
| struct sk_buff *skb; |
| }; |
| |
| static inline struct ath10k_usb *ath10k_usb_priv(struct ath10k *ar) |
| { |
| return (struct ath10k_usb *)ar->drv_priv; |
| } |
| |
| /* The source file. */ |
| |
| static void ath10k_usb_post_recv_transfers(struct ath10k *ar, |
| struct ath10k_usb_pipe *recv_pipe); |
| |
| struct ath10k_urb_context * |
| ath10k_usb_alloc_urb_from_pipe(struct ath10k_usb_pipe *pipe); |
| |
| void ath10k_usb_free_urb_to_pipe(struct ath10k_usb_pipe *pipe, |
| struct ath10k_urb_context *urb_context); |
| |
| static int ath10k_usb_hif_tx_sg(struct ath10k *ar, u8 pipe_id, |
| struct ath10k_hif_sg_item *items, int n_items) |
| { |
| struct ath10k_usb *ar_usb = ath10k_usb_priv(ar); |
| struct ath10k_usb_pipe *pipe = &ar_usb->pipes[pipe_id]; |
| struct ath10k_urb_context *urb_context; |
| struct sk_buff *skb; |
| struct urb *urb; |
| int ret, i; |
| |
| for (i = 0; i < n_items; i++) { |
| urb_context = ath10k_usb_alloc_urb_from_pipe(pipe); |
| if (!urb_context) { |
| ret = -ENOMEM; |
| goto err; |
| } |
| |
| skb = items[i].transfer_context; |
| urb_context->skb = skb; |
| |
| urb = usb_alloc_urb(0, GFP_ATOMIC); /* { dg-message "allocated here" } */ |
| if (!urb) { |
| ret = -ENOMEM; |
| goto err_free_urb_to_pipe; |
| } |
| |
| /* TODO: these are disabled, otherwise we conservatively |
| assume that they could free urb. */ |
| #if 0 |
| usb_fill_bulk_urb(urb, |
| ar_usb->udev, |
| pipe->usb_pipe_handle, |
| skb->data, |
| skb->len, |
| ath10k_usb_transmit_complete, urb_context); |
| if (!(skb->len % pipe->max_packet_size)) { |
| /* hit a max packet boundary on this pipe */ |
| urb->transfer_flags |= URB_ZERO_PACKET; |
| } |
| |
| usb_anchor_urb(urb, &pipe->urb_submitted); |
| #endif |
| /* TODO: initial argument disabled, otherwise we conservatively |
| assume that it could free urb. */ |
| ret = usb_submit_urb(/*urb, */GFP_ATOMIC); |
| if (ret) { /* TODO: why doesn't it show this condition at default verbosity? */ |
| ath10k_dbg(ar, ATH10K_DBG_USB_BULK, |
| "usb bulk transmit failed: %d\n", ret); |
| |
| /* TODO: this is disabled, otherwise we conservatively |
| assume that it could free urb. */ |
| #if 0 |
| usb_unanchor_urb(urb); |
| #endif |
| |
| ret = -EINVAL; |
| /* Leak of urb happens here. */ |
| goto err_free_urb_to_pipe; |
| } |
| |
| usb_free_urb(urb); /* { dg-bogus "double-'usb_free_urb' of 'urb'" } */ |
| } |
| |
| return 0; |
| |
| err_free_urb_to_pipe: |
| ath10k_usb_free_urb_to_pipe(urb_context->pipe, urb_context); |
| err: |
| return ret; /* { dg-warning "leak of 'urb'" } */ |
| } |
| |
| static const struct ath10k_hif_ops ath10k_usb_hif_ops = { |
| .tx_sg = ath10k_usb_hif_tx_sg, |
| }; |
| |
| /* Simulate code to register the callback. */ |
| extern void callback_registration (const void *); |
| int ath10k_usb_probe(void) |
| { |
| callback_registration(&ath10k_usb_hif_ops); |
| } |
| |
| |
| /* The original source file ends with: |
| MODULE_AUTHOR("Atheros Communications, Inc."); |
| MODULE_DESCRIPTION("Driver support for Qualcomm Atheros 802.11ac WLAN USB devices"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| */ |