Last active
August 1, 2023 07:58
-
-
Save drakedevel/6cecde47f803d003629cf724f3a4bee8 to your computer and use it in GitHub Desktop.
Tool to demonstrate a bug in the SY-T18 MicroSD card reader.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Author: Andrew Drake <adrake@adrake.org> | |
* Please feel free to email me or comment on this Gist if you have any questions. | |
* | |
* On the SY-T18 MicroSD reader, there is a bug when the following reads occur | |
* back-to-back: | |
* | |
* 1. 129 or more sectors, anywhere | |
* 2. 1 or more sectors, exactly 128 sectors after the end of the previous read | |
* | |
* If this occurs, the second read will read the wrong location on disk. It will | |
* return the data immediately following the first read, instead of 128 sectors | |
* later. This obviously results in data corruption. | |
* | |
* This program writes a short test pattern to the given block device and reads | |
* it back with the above pattern. The pattern includes the sector the pattern | |
* was written to so that mis-reads can be detected. | |
* | |
* To run the test, compile and run this file on a Linux system: | |
* | |
* $ gcc -o ./sd_reader_bug sd_reader_bug.c | |
* $ sudo ./sd_reader_bug DEVICE OFFSET where | |
* | |
* Where DEVICE is a path to the raw block device with the SD reader to test | |
* (e.g. /dev/sdd) and OFFSET is a sector offset to run the test at (e.g. 0). | |
* Make sure there is nothing important on the SD card since part of it will be | |
* overwritten! | |
* | |
* A typical (failing) test result from a SY-T18 will look like this: | |
* | |
* $ sudo ./sd_reader_bug /dev/sdd 0 | |
* Wrote 258 sectors starting at sector 0 | |
* Read 129 sectors at sector 0 | |
* Read 1 sectors at sector 257 | |
* FAIL: At sector 257, read 129 != 257 (off by 128) | |
* sd_reader_bug: TEST FAILED | |
* | |
* Whereas it will pass on any other type of reader: | |
* | |
* $ sudo ./sd_reader_bug /dev/sdd 0 | |
* Wrote 265 sectors starting at sector 0 | |
* Read 129 sectors at sector 0 | |
* Read 8 sectors at sector 257 | |
* TEST PASSED | |
* | |
* You can change READ1_SECTORS and READ2_SECTORS below to larger values and the | |
* test should still fail on a SY-T18, but smaller values won't trigger the bug. | |
* | |
* Some notes: | |
* - If another process or the operating system reads from the SD card during the | |
* test, the test will pass. This is especially likely to happen in the first | |
* few seconds after plugging in the reader. If you get a pass on a SY-T18 | |
* shortly after plugging it in, try running the test again. | |
* | |
* - The test may also pass unexpectedly if you run it multiple times in rapid | |
* succession. Wait a second between attempts to avoid this. | |
* | |
* - I have not tested if this bug occurs with writes as well as reads, but it | |
* probably does. | |
*/ | |
#define _FILE_OFFSET_BITS 64 | |
#define _GNU_SOURCE | |
#include <err.h> | |
#include <fcntl.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <linux/fs.h> | |
#include <sys/ioctl.h> | |
#define SECTOR_SIZE 512 | |
#define READ1_SECTORS 129 | |
#define SKIP_SECTORS 128 | |
#define READ2_SECTORS 1 | |
#define TOTAL_SECTORS (READ1_SECTORS + SKIP_SECTORS + READ2_SECTORS) | |
typedef char sector_t[SECTOR_SIZE]; | |
static void read_and_check(int fd, int64_t start, int64_t count, void *buf) { | |
int result = pread(fd, buf, count * SECTOR_SIZE, start * SECTOR_SIZE); | |
if (result < 0) | |
err(1, "Failed to read at sector %ld", start); | |
if (result < count * SECTOR_SIZE) | |
errx(1, "Unexpected short read at sector %ld", start); | |
printf("Read %ld sectors at sector %ld\n", count, start); | |
int failed = 0; | |
for (int64_t i = 0; i < count; i++) { | |
int64_t actual = *(int64_t *)((sector_t *)buf + i); | |
if (actual != start + i) { | |
printf(" FAIL: At sector %ld, read %ld != %ld (off by %ld)\n", | |
start + i, actual, start + i, start + i - actual); | |
failed = 1; | |
} | |
} | |
if (failed) | |
errx(1, "TEST FAILED"); | |
} | |
int main(int argc, const char** argv) { | |
// Argument parsing and validation | |
if (argc != 3) | |
errx(1, "Usage: sd_reader_bug <device> <start_sector>"); | |
const char* dev_path = argv[1]; | |
char* start_sector_endptr; | |
int64_t start_sector = strtoll(argv[2], &start_sector_endptr, 0); | |
if (!*argv[2] || *start_sector_endptr || start_sector < 0) | |
errx(1, "Invalid start sector"); | |
// Open specified device with direct I/O and confirm sector size | |
int fd = open(dev_path, O_RDWR | O_DIRECT | O_EXCL | O_SYNC); | |
if (fd < 0) | |
err(1, "Could not open device %s", dev_path); | |
int sector_size; | |
int result = ioctl(fd, BLKSSZGET, §or_size); | |
if (result) | |
err(1, "Failed to get block device sector size"); | |
if (sector_size != SECTOR_SIZE) | |
errx(1, "Block device has unexpected sector size %d != %d", sector_size, SECTOR_SIZE); | |
// Write test pattern at specified offset, with the absolute sector number | |
// at the start of each sector to allow us to detect misreads | |
void *buf; | |
result = posix_memalign(&buf, SECTOR_SIZE, SECTOR_SIZE * TOTAL_SECTORS); | |
if (result) | |
errx(1, "Failed to allocate I/O buffer: %s", strerror(result)); | |
memset(buf, 0, SECTOR_SIZE * TOTAL_SECTORS); | |
for (int i = 0; i < TOTAL_SECTORS; i++) | |
*(int64_t *)((sector_t *)buf + i) = start_sector + i; | |
result = pwrite(fd, buf, SECTOR_SIZE * TOTAL_SECTORS, start_sector * SECTOR_SIZE); | |
if (result < 0) | |
err(1, "Failed to write test pattern at sector %ld", start_sector); | |
if (result < SECTOR_SIZE * TOTAL_SECTORS) | |
errx(1, "Unexpected short write"); | |
printf("Wrote %d sectors starting at sector %ld\n", TOTAL_SECTORS, start_sector); | |
// Perform reads to trigger the bug, and check for the bug's presence | |
read_and_check(fd, start_sector, READ1_SECTORS, buf); | |
read_and_check(fd, start_sector + READ1_SECTORS + SKIP_SECTORS, READ2_SECTORS, buf); | |
printf("TEST PASSED\n"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment