Commit message: read_boot(): Handle excessive FAT size specifications

The variable used for storing the FAT size (in bytes) was an unsigned
int. Since the size in sectors read from the BPB was not sufficiently
checked, this could end up being zero after multiplying it with the
sector size while some offsets still stayed excessive. Ultimately it
would cause segfaults when accessing FAT entries for which no memory
was allocated.

Make it more robust by changing the types used to store FAT size to
off_t and abort if there is no room for data clusters. Additionally
check that FAT size is not specified as zero.

Fixes #25 and fixes #26.

Reported-by: Hanno Böck
Signed-off-by: Andreas Bombe <aeb@debian.org>
CVE: CVE-2016-4804
CWE: CWE-119
Vulnerability description: The read_boot function in boot.c in dosfstools before 4.0 allows attackers to cause a denial of service (crash) via a crafted filesystem, which triggers a heap-based buffer overflow in the (1) read_fat function or an out-of-bounds heap read in (2) get_fat function.

Project: dosfstools
File: boot.c

static void check_backup_boot(DOS_FS * fs, struct boot_sector *b, int lss)
{
    struct boot_sector b2;

    if (!fs->backupboot_start) {
	printf("There is no backup boot sector.\n");
	if (le16toh(b->reserved) < 3) {
	    printf("And there is no space for creating one!\n");
	    return;
	}
	if (interactive)
	    printf("1) Create one\n2) Do without a backup\n");
	else
	    printf("  Auto-creating backup boot block.\n");
	if (!interactive || get_key("12", "?") == '1') {
	    int bbs;
	    /* The usual place for the backup boot sector is sector 6. Choose
	     * that or the last reserved sector. */
	    if (le16toh(b->reserved) >= 7 && le16toh(b->info_sector) != 6)
		bbs = 6;
	    else {
		bbs = le16toh(b->reserved) - 1;
		if (bbs == le16toh(b->info_sector))
		    --bbs;	/* this is never 0, as we checked reserved >= 3! */
	    }
	    fs->backupboot_start = bbs * lss;
	    b->backup_boot = htole16(bbs);
	    fs_write(fs->backupboot_start, sizeof(*b), b);
	    fs_write(offsetof(struct boot_sector, backup_boot),
		     sizeof(b->backup_boot), &b->backup_boot);
	    printf("Created backup of boot sector in sector %d\n", bbs);
	    return;
	} else
	    return;
    }

    fs_read(fs->backupboot_start, sizeof(b2), &b2);
    if (memcmp(b, &b2, sizeof(b2)) != 0) {
	/* there are any differences */
	uint8_t *p, *q;
	int i, pos, first = 1;
	char buf[20];

	printf("There are differences between boot sector and its backup.\n");
	printf("This is mostly harmless. Differences: (offset:original/backup)\n  ");
	pos = 2;
	for (p = (uint8_t *) b, q = (uint8_t *) & b2, i = 0; i < sizeof(b2);
	     ++p, ++q, ++i) {
	    if (*p != *q) {
		sprintf(buf, "%s%u:%02x/%02x", first ? "" : ", ",
			(unsigned)(p - (uint8_t *) b), *p, *q);
		if (pos + strlen(buf) > 78)
		    printf("\n  "), pos = 2;
		printf("%s", buf);
		pos += strlen(buf);
		first = 0;
	    }
	}
	printf("\n");

	if (interactive)
	    printf("1) Copy original to backup\n"
		   "2) Copy backup to original\n" "3) No action\n");
	else
	    printf("  Not automatically fixing this.\n");
	switch (interactive ? get_key("123", "?") : '3') {
	case '1':
	    fs_write(fs->backupboot_start, sizeof(*b), b);
	    break;
	case '2':
	    fs_write(0, sizeof(b2), &b2);
	    break;
	default:
	    break;
	}
    }
}

Project: dosfstools
File: boot.c

static void check_fat_state_bit(DOS_FS * fs, void *b)
{
    if (fs->fat_bits == 32) {
	struct boot_sector *b32 = b;

	if (b32->reserved3 & FAT_STATE_DIRTY) {
	    printf("0x41: ");
	    if (print_fat_dirty_state() == '1') {
		b32->reserved3 &= ~FAT_STATE_DIRTY;
		fs_write(0, sizeof(*b32), b32);
	    }
	}
    } else {
	struct boot_sector_16 *b16 = b;

	if (b16->reserved2 & FAT_STATE_DIRTY) {
	    printf("0x25: ");
	    if (print_fat_dirty_state() == '1') {
		b16->reserved2 &= ~FAT_STATE_DIRTY;
		fs_write(0, sizeof(*b16), b16);
	    }
	}
    }
}

Project: dosfstools
File: boot.c

off_t find_volume_de(DOS_FS * fs, DIR_ENT * de)
{
    uint32_t cluster;
    off_t offset;
    int i;

    if (fs->root_cluster) {
	for (cluster = fs->root_cluster;
	     cluster != 0 && cluster != -1;
	     cluster = next_cluster(fs, cluster)) {
	    offset = cluster_start(fs, cluster);
	    for (i = 0; i * sizeof(DIR_ENT) < fs->cluster_size; i++) {
		fs_read(offset, sizeof(DIR_ENT), de);
		if (de->attr != VFAT_LN_ATTR && de->attr & ATTR_VOLUME)
		    return offset;
		offset += sizeof(DIR_ENT);
	    }
	}
    } else {
	for (i = 0; i < fs->root_entries; i++) {
	    offset = fs->root_start + i * sizeof(DIR_ENT);
	    fs_read(offset, sizeof(DIR_ENT), de);
	    if (de->attr != VFAT_LN_ATTR && de->attr & ATTR_VOLUME)
		return offset;
	}
    }

    return 0;
}

Project: dosfstools
File: boot.c

static const char *get_media_descr(unsigned char media)
{
    int i;

    for (i = 0; i < sizeof(mediabytes) / sizeof(*mediabytes); ++i) {
	if (mediabytes[i].media == media)
	    return (mediabytes[i].descr);
    }
    return ("undefined");
}

Project: dosfstools
File: boot.c

static void init_fsinfo(struct info_sector *i)
{
    i->magic = htole32(0x41615252);
    i->signature = htole32(0x61417272);
    i->free_clusters = htole32(-1);
    i->next_cluster = htole32(2);
    i->boot_sign = htole16(0xaa55);
}

Project: dosfstools
File: boot.c

static char print_fat_dirty_state(void)
{
    printf("Dirty bit is set. Fs was not properly unmounted and"
	   " some data may be corrupt.\n");

    if (interactive) {
	printf("1) Remove dirty bit\n" "2) No action\n");
	return get_key("12", "?");
    } else
	printf(" Automatically removing dirty bit.\n");
    return '1';
}

Project: dosfstools
File: boot.c

static void read_fsinfo(DOS_FS * fs, struct boot_sector *b, int lss)
{
    struct info_sector i;

    if (!b->info_sector) {
	printf("No FSINFO sector\n");
	if (interactive)
	    printf("1) Create one\n2) Do without FSINFO\n");
	else
	    printf("  Not automatically creating it.\n");
	if (interactive && get_key("12", "?") == '1') {
	    /* search for a free reserved sector (not boot sector and not
	     * backup boot sector) */
	    uint32_t s;
	    for (s = 1; s < le16toh(b->reserved); ++s)
		if (s != le16toh(b->backup_boot))
		    break;
	    if (s > 0 && s < le16toh(b->reserved)) {
		init_fsinfo(&i);
		fs_write((off_t)s * lss, sizeof(i), &i);
		b->info_sector = htole16(s);
		fs_write(offsetof(struct boot_sector, info_sector),
			 sizeof(b->info_sector), &b->info_sector);
		if (fs->backupboot_start)
		    fs_write(fs->backupboot_start +
			     offsetof(struct boot_sector, info_sector),
			     sizeof(b->info_sector), &b->info_sector);
	    } else {
		printf("No free reserved sector found -- "
		       "no space for FSINFO sector!\n");
		return;
	    }
	} else
	    return;
    }

    fs->fsinfo_start = le16toh(b->info_sector) * lss;
    fs_read(fs->fsinfo_start, sizeof(i), &i);

    if (i.magic != htole32(0x41615252) ||
	i.signature != htole32(0x61417272) || i.boot_sign != htole16(0xaa55)) {
	printf("FSINFO sector has bad magic number(s):\n");
	if (i.magic != htole32(0x41615252))
	    printf("  Offset %llu: 0x%08x != expected 0x%08x\n",
		   (unsigned long long)offsetof(struct info_sector, magic),
		   le32toh(i.magic), 0x41615252);
	if (i.signature != htole32(0x61417272))
	    printf("  Offset %llu: 0x%08x != expected 0x%08x\n",
		   (unsigned long long)offsetof(struct info_sector, signature),
		   le32toh(i.signature), 0x61417272);
	if (i.boot_sign != htole16(0xaa55))
	    printf("  Offset %llu: 0x%04x != expected 0x%04x\n",
		   (unsigned long long)offsetof(struct info_sector, boot_sign),
		   le16toh(i.boot_sign), 0xaa55);
	if (interactive)
	    printf("1) Correct\n2) Don't correct (FSINFO invalid then)\n");
	else
	    printf("  Auto-correcting it.\n");
	if (!interactive || get_key("12", "?") == '1') {
	    init_fsinfo(&i);
	    fs_write(fs->fsinfo_start, sizeof(i), &i);
	} else
	    fs->fsinfo_start = 0;
    }

    if (fs->fsinfo_start)
	fs->free_clusters = le32toh(i.free_clusters);
}

Project: dosfstools
File: boot.c

static void write_boot_label(DOS_FS * fs, char *label)
{
    if (fs->fat_bits == 12 || fs->fat_bits == 16) {
	struct boot_sector_16 b16;

	fs_read(0, sizeof(b16), &b16);
	if (b16.extended_sig != 0x29) {
	    b16.extended_sig = 0x29;
	    b16.serial = 0;
	    memmove(b16.fs_type, fs->fat_bits == 12 ? "FAT12   " : "FAT16   ",
		    8);
	}
	memmove(b16.label, label, 11);
	fs_write(0, sizeof(b16), &b16);
    } else if (fs->fat_bits == 32) {
	struct boot_sector b;

	fs_read(0, sizeof(b), &b);
	if (b.extended_sig != 0x29) {
	    b.extended_sig = 0x29;
	    b.serial = 0;
	    memmove(b.fs_type, "FAT32   ", 8);
	}
	memmove(b.label, label, 11);
	fs_write(0, sizeof(b), &b);
	if (fs->backupboot_start)
	    fs_write(fs->backupboot_start, sizeof(b), &b);
    }
}

Project: dosfstools
File: boot.c

void write_label(DOS_FS * fs, char *label)
{
    int l = strlen(label);

    while (l < 11)
	label[l++] = ' ';

    write_boot_label(fs, label);
    write_volume_label(fs, label);
}

Project: dosfstools
File: boot.c

static void write_volume_label(DOS_FS * fs, char *label)
{
    time_t now = time(NULL);
    struct tm *mtime = localtime(&now);
    off_t offset;
    int created;
    DIR_ENT de;

    created = 0;
    offset = find_volume_de(fs, &de);
    if (offset == 0) {
	created = 1;
	offset = alloc_rootdir_entry(fs, &de, label);
    }
    memcpy(de.name, label, 11);
    de.time = htole16((unsigned short)((mtime->tm_sec >> 1) +
				       (mtime->tm_min << 5) +
				       (mtime->tm_hour << 11)));
    de.date = htole16((unsigned short)(mtime->tm_mday +
				       ((mtime->tm_mon + 1) << 5) +
				       ((mtime->tm_year - 80) << 9)));
    if (created) {
	de.attr = ATTR_VOLUME;
	de.ctime_ms = 0;
	de.ctime = de.time;
	de.cdate = de.date;
	de.adate = de.date;
	de.starthi = 0;
	de.start = 0;
	de.size = 0;
    }

    fs_write(offset, sizeof(DIR_ENT), &de);
}