/* 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 */ /* 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"); */