In alauda_read_data() and alauda_write_lba(), alauda_ensure_map_for_zone() is called to ensure that the zone block mapping tables (lba_to_pba and pba_to_lba) are initialized and populated. However, alauda_ensure_map_for_zone() had a void return type and ignored the return value of alauda_read_map(). When alauda_read_map() failed (such as on USB transfer error or memory allocation failure), MEDIA_INFO(us).lba_to_pba[zone] remained NULL. Subsequent accesses to MEDIA_INFO(us).lba_to_pba[zone][lba_offset] resulted in a NULL pointer dereference: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: 0010:alauda_read_data drivers/usb/storage/alauda.c:972 [inline] RIP: 0010:alauda_transport+0xd68/0x43a0 drivers/usb/storage/alauda.c:1187 Call Trace: usb_stor_invoke_transport+0x115/0x1a70 drivers/usb/storage/transport.c:611 usb_stor_control_thread+0x44c/0x8f0 drivers/usb/storage/usb.c:462 kthread+0x388/0x470 kernel/kthread.c:436 ret_from_fork+0x491/0xad0 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Additionally, commit 16637fea001a ("usb-storage: alauda: Check whether the media is initialized") added the media_initialized flag to struct alauda_info. Because Alauda card readers support two independent media ports (xD and SmartMedia) mapped to LUN 0 and LUN 1, setting the flag on the shared device structure upon initializing LUN 0 caused alauda_check_media() to skip calling alauda_init_media() when handling requests for LUN 1. Consequently, port 1's uzonesize remained 0, causing a divide-by-zero error (lba / uzonesize) in alauda_read_data(), alauda_write_lba(), and alauda_write_data(). Furthermore, alauda_read_data() calculated lba / uzonesize and invoked alauda_ensure_map_for_zone() before validating whether lba exceeded max_lba. Fix these issues by: - Moving the media_initialized boolean from struct alauda_info to struct alauda_media_info so that each port's initialization status is tracked independently, and clearing it when media absence or removal is detected. - Updating alauda_ensure_map_for_zone() to return an error status code, verifying that mapping table pointers are non-NULL, and propagating the result of alauda_read_map(). - Checking the return code of alauda_ensure_map_for_zone() in alauda_read_data() and alauda_write_lba() and aborting the transfer on failure. - Adding sanity checks for uzonesize before division operations in alauda_read_data(), alauda_write_lba(), and alauda_write_data(), and performing the lba >= max_lba check before zone calculation in alauda_read_data(). - Freeing top-level mapping arrays and resetting capacity and uzonesize in alauda_free_maps(), and ensuring alauda_free_maps() is called on error paths in alauda_init_media(). Fixes: e80b0fade09e ("[PATCH] USB Storage: add alauda support") Assisted-by: Gemini:gemini-3.7-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+ccc9a7cb39fa1af827ea@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ccc9a7cb39fa1af827ea Link: https://syzkaller.appspot.com/ai_job?id=f19433e4-a93f-4b85-a1da-d33b9943d037 To: "Greg Kroah-Hartman" To: To: "Alan Stern" To: To: "Matthew Dharm" Cc: --- diff --git a/drivers/usb/storage/alauda.c b/drivers/usb/storage/alauda.c index 691fe4700..a2a333419 100644 --- a/drivers/usb/storage/alauda.c +++ b/drivers/usb/storage/alauda.c @@ -96,6 +96,8 @@ struct alauda_media_info { u16 **lba_to_pba; /* logical to physical block map */ u16 **pba_to_lba; /* physical to logical block map */ + + bool media_initialized; }; struct alauda_info { @@ -105,8 +107,6 @@ struct alauda_info { unsigned char sense_key; unsigned long sense_asc; /* additional sense code */ unsigned long sense_ascq; /* additional sense code qualifier */ - - bool media_initialized; }; #define short_pack(lsb,msb) ( ((u16)(lsb)) | ( ((u16)(msb))<<8 ) ) @@ -290,17 +290,25 @@ static void alauda_free_maps (struct alauda_media_info *media_info) unsigned int num_zones = media_info->capacity >> shift; unsigned int i; - if (media_info->lba_to_pba != NULL) + if (media_info->lba_to_pba != NULL) { for (i = 0; i < num_zones; i++) { kfree(media_info->lba_to_pba[i]); media_info->lba_to_pba[i] = NULL; } + kfree(media_info->lba_to_pba); + media_info->lba_to_pba = NULL; + } - if (media_info->pba_to_lba != NULL) + if (media_info->pba_to_lba != NULL) { for (i = 0; i < num_zones; i++) { kfree(media_info->pba_to_lba[i]); media_info->pba_to_lba[i] = NULL; } + kfree(media_info->pba_to_lba); + media_info->pba_to_lba = NULL; + } + media_info->capacity = 0; + media_info->uzonesize = 0; } /* @@ -441,11 +449,15 @@ static int alauda_init_media(struct us_data *us) + MEDIA_INFO(us).blockshift + MEDIA_INFO(us).pageshift); MEDIA_INFO(us).pba_to_lba = kcalloc(num_zones, sizeof(u16*), GFP_NOIO); MEDIA_INFO(us).lba_to_pba = kcalloc(num_zones, sizeof(u16*), GFP_NOIO); - if (MEDIA_INFO(us).pba_to_lba == NULL || MEDIA_INFO(us).lba_to_pba == NULL) + if (MEDIA_INFO(us).pba_to_lba == NULL || MEDIA_INFO(us).lba_to_pba == NULL) { + alauda_free_maps(&MEDIA_INFO(us)); return USB_STOR_TRANSPORT_ERROR; + } - if (alauda_reset_media(us) != USB_STOR_XFER_GOOD) + if (alauda_reset_media(us) != USB_STOR_XFER_GOOD) { + alauda_free_maps(&MEDIA_INFO(us)); return USB_STOR_TRANSPORT_ERROR; + } return USB_STOR_TRANSPORT_GOOD; } @@ -457,6 +469,7 @@ static int alauda_init_media(struct us_data *us) static int alauda_check_media(struct us_data *us) { struct alauda_info *info = (struct alauda_info *) us->extra; + struct alauda_media_info *media_info = &MEDIA_INFO(us); unsigned char *status = us->iobuf; int rc; @@ -470,7 +483,8 @@ static int alauda_check_media(struct us_data *us) if ((status[0] & 0x80) || ((status[0] & 0x1F) == 0x10) || ((status[1] & 0x01) == 0)) { usb_stor_dbg(us, "No media, or door open\n"); - alauda_free_maps(&MEDIA_INFO(us)); + alauda_free_maps(media_info); + media_info->media_initialized = false; info->sense_key = 0x02; info->sense_asc = 0x3A; info->sense_ascq = 0x00; @@ -478,12 +492,13 @@ static int alauda_check_media(struct us_data *us) } /* Check for media change */ - if (status[0] & 0x08 || !info->media_initialized) { + if (status[0] & 0x08 || !media_info->media_initialized) { usb_stor_dbg(us, "Media change detected\n"); - alauda_free_maps(&MEDIA_INFO(us)); + alauda_free_maps(media_info); + media_info->media_initialized = false; rc = alauda_init_media(us); if (rc == USB_STOR_TRANSPORT_GOOD) - info->media_initialized = true; + media_info->media_initialized = true; info->sense_key = UNIT_ATTENTION; info->sense_asc = 0x28; info->sense_ascq = 0x00; @@ -689,11 +704,16 @@ static int alauda_read_map(struct us_data *us, unsigned int zone) * Checks to see whether we have already mapped a certain zone * If we haven't, the map is generated */ -static void alauda_ensure_map_for_zone(struct us_data *us, unsigned int zone) +static int alauda_ensure_map_for_zone(struct us_data *us, unsigned int zone) { + if (!MEDIA_INFO(us).lba_to_pba || !MEDIA_INFO(us).pba_to_lba) + return USB_STOR_TRANSPORT_ERROR; + if (MEDIA_INFO(us).lba_to_pba[zone] == NULL || MEDIA_INFO(us).pba_to_lba[zone] == NULL) - alauda_read_map(us, zone); + return alauda_read_map(us, zone); + + return USB_STOR_TRANSPORT_GOOD; } /* @@ -819,11 +839,19 @@ static int alauda_write_lba(struct us_data *us, u16 lba, unsigned int zonesize = MEDIA_INFO(us).zonesize; unsigned int pagesize = MEDIA_INFO(us).pagesize; unsigned int blocksize = MEDIA_INFO(us).blocksize; - unsigned int lba_offset = lba % uzonesize; + unsigned int lba_offset; unsigned int new_pba_offset; - unsigned int zone = lba / uzonesize; + unsigned int zone; - alauda_ensure_map_for_zone(us, zone); + if (!uzonesize) + return USB_STOR_TRANSPORT_ERROR; + + lba_offset = lba % uzonesize; + zone = lba / uzonesize; + + result = alauda_ensure_map_for_zone(us, zone); + if (result != USB_STOR_TRANSPORT_GOOD) + return result; pba = MEDIA_INFO(us).lba_to_pba[zone][lba_offset]; if (pba == 1) { @@ -927,6 +955,9 @@ static int alauda_read_data(struct us_data *us, unsigned long address, struct scatterlist *sg; int result; + if (!uzonesize) + return USB_STOR_TRANSPORT_ERROR; + /* * Since we only read in one block at a time, we have to create * a bounce buffer and move the data a piece at a time between the @@ -950,11 +981,10 @@ static int alauda_read_data(struct us_data *us, unsigned long address, sg = NULL; while (sectors > 0) { - unsigned int zone = lba / uzonesize; /* integer division */ - unsigned int lba_offset = lba - (zone * uzonesize); + unsigned int zone; + unsigned int lba_offset; unsigned int pages; u16 pba; - alauda_ensure_map_for_zone(us, zone); /* Not overflowing capacity? */ if (lba >= max_lba) { @@ -964,6 +994,13 @@ static int alauda_read_data(struct us_data *us, unsigned long address, break; } + zone = lba / uzonesize; /* integer division */ + lba_offset = lba - (zone * uzonesize); + + result = alauda_ensure_map_for_zone(us, zone); + if (result != USB_STOR_TRANSPORT_GOOD) + break; + /* Find number of pages we can read in this block */ pages = min(sectors, blocksize - page); len = pages << pageshift; @@ -1021,6 +1058,9 @@ static int alauda_write_data(struct us_data *us, unsigned long address, u16 lba, max_lba; int result; + if (!MEDIA_INFO(us).uzonesize) + return USB_STOR_TRANSPORT_ERROR; + /* * Since we don't write the user data directly to the device, * we have to create a bounce buffer and move the data a piece base-commit: 8d3ae59288f1e7d58d76558a6ee96d533bc5019f -- This is an AI-generated patch subject to moderation. Reply with '#syz upstream' to Sign-off the patch as a human author and send it to the upstream kernel mailing lists. Reply with '#syz reject' to reject it ('#syz unreject' to undo). See https://goo.gle/syzbot-ai-patches for information about AI-generated patches. You can comment on the patch as usual, syzbot will try to address the comments and send a new version of the patch if necessary. syzbot engineers can be reached at syzkaller@googlegroups.com.