/* * Userspace reproducer for the past-the-end iterator behavior in * ast_udc_ep_dequeue() (drivers/usb/gadget/udc/aspeed_udc.c). * * Aspeed UDC is BMC/ARM hardware. Rather than bringing up a full SoC * emulation, this program extracts the dequeue function's logic into * userspace using mock structs whose layout (req at offset 0, queue * immediately after) matches the kernel definition. It then runs both * the existing code path and the proposed fix on the same crafted input. * * Build: cc -O0 -g poc_aspeed_udc.c -o poc_aspeed_udc * Run: ./poc_aspeed_udc (existing code, returns 42) * ./poc_aspeed_udc patched (proposed fix, returns 0) */ #define _GNU_SOURCE #include #include #include #include /* Minimal mock of the kernel list_head and container_of. */ struct list_head { struct list_head *next, *prev; }; #define container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member))) #define list_first_entry(ptr, type, member) \ container_of((ptr)->next, type, member) #define list_next_entry(pos, member) \ container_of((pos)->member.next, typeof(*(pos)), member) #define list_entry_is_head(pos, head, member) \ (&(pos)->member == (head)) #define list_for_each_entry(pos, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member); \ !list_entry_is_head(pos, head, member); \ pos = list_next_entry(pos, member)) static void list_init(struct list_head *h) { h->next = h->prev = h; } /* Mock structs. Only field order matters: req at offset 0, queue * immediately after. */ struct usb_request { void *buf; unsigned length; int status; }; struct ast_udc_request { struct usb_request req; struct list_head queue; int pad; }; struct ast_udc_ep { struct list_head queue; }; /* Existing code path from aspeed_udc.c around line 691. Locks and * the ast_udc_done() callback are elided since the past-the-end * behavior is independent of them. */ static int ast_udc_ep_dequeue_existing(struct ast_udc_ep *ep, struct usb_request *_req) { struct ast_udc_request *req; int rc = 0; list_for_each_entry(req, &ep->queue, queue) { if (&req->req == _req) { /* list_del_init + ast_udc_done + set status here */ break; } } /* When the loop finds no match, req is past-the-end. Reading * &req->req is undefined per C11; the resulting check is a * property of heap layout rather than the queue contents. */ if (&req->req != _req) rc = -22; /* -EINVAL */ return rc; } /* Proposed fix using the separate iter cursor pattern shared by the * other UDC drivers in the same directory (e.g. dummy_hcd.c). */ static int ast_udc_ep_dequeue_patched(struct ast_udc_ep *ep, struct usb_request *_req) { struct ast_udc_request *req = NULL, *iter; list_for_each_entry(iter, &ep->queue, queue) { if (&iter->req != _req) continue; req = iter; break; } if (!req) return -22; /* -EINVAL */ /* list_del_init + ast_udc_done + set status here */ return 0; } int main(int argc, char **argv) { int use_patched = (argc > 1 && !strcmp(argv[1], "patched")); struct ast_udc_ep ep; list_init(&ep.queue); /* An empty queue forces the existing code's iterator past the end. * past_end is the synthetic ast_udc_request pointer the loop will * leave behind. Because req is the first member, &past_end->req * has the same numeric value as past_end itself. */ struct ast_udc_request *past_end = container_of(&ep.queue, struct ast_udc_request, queue); struct usb_request *fake_req = &past_end->req; printf("[setup] ep.queue=%p (head)\n", (void *)&ep.queue); printf("[setup] past_end=%p\n", (void *)past_end); printf("[setup] fake_req=%p\n", (void *)fake_req); int rc; if (use_patched) { rc = ast_udc_ep_dequeue_patched(&ep, fake_req); printf("[probe] patched rc=%d\n", rc); } else { rc = ast_udc_ep_dequeue_existing(&ep, fake_req); printf("[probe] existing rc=%d\n", rc); } if (rc == 0) { printf("[result] returned 0 (success) on empty queue without " "removing anything\n"); return 42; } printf("[result] returned %d (rejected)\n", rc); return 0; }