Skip to content

Instantly share code, notes, and snippets.

@drakedevel
Last active August 1, 2023 07:58
Show Gist options
  • Save drakedevel/6cecde47f803d003629cf724f3a4bee8 to your computer and use it in GitHub Desktop.
Save drakedevel/6cecde47f803d003629cf724f3a4bee8 to your computer and use it in GitHub Desktop.
Tool to demonstrate a bug in the SY-T18 MicroSD card reader.
/*
* 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, &sector_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