/*
* linux.c
* Detection of Linux file systems and boot codes
*
* Copyright (c) 2003-2006 Christoph Pfisterer
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "global.h"
/*
* ext2/ext3 file system
*/
void detect_ext23(SECTION *section, int level)
{
unsigned char *buf;
char s[256];
u4 blocksize;
u8 blockcount;
if (get_buffer(section, 1024, 1024, (void **)&buf) < 1024)
return;
if (get_le_short(buf + 56) == 0xEF53) {
if (get_le_long(buf + 96) & 0x0008) /* JOURNAL_DEV flag */
print_line(level, "Ext3 external journal");
else if (get_le_long(buf + 92) & 0x0004) /* HAS_JOURNAL flag */
print_line(level, "Ext3 file system");
else
print_line(level, "Ext2 file system");
get_string(buf + 120, 16, s);
if (s[0])
print_line(level + 1, "Volume name \"%s\"", s);
format_uuid(buf + 104, s);
print_line(level + 1, "UUID %s", s);
get_string(buf + 136, 64, s);
if (s[0])
print_line(level + 1, "Last mounted at \"%s\"", s);
blocksize = 1024 << get_le_long(buf + 24);
blockcount = get_le_long(buf + 4);
format_blocky_size(s, blockcount, blocksize, "blocks", NULL);
print_line(level + 1, "Volume size %s", s);
/* 76 4 s_rev_level */
/* 62 2 s_minor_rev_level */
/* 72 4 s_creator_os */
/* 92 3x4 s_feature_compat, s_feature_incompat, s_feature_ro_compat */
}
}
/*
* ReiserFS file system
*/
void detect_reiser(SECTION *section, int level)
{
unsigned char *buf;
int i, at, newformat;
int offsets[3] = { 8, 64, -1 };
char s[256];
u8 blockcount;
u4 blocksize;
for (i = 0; offsets[i] >= 0; i++) {
at = offsets[i];
if (get_buffer(section, at * 1024, 1024, (void **)&buf) < 1024)
continue;
/* check signature */
if (memcmp(buf + 52, "ReIsErFs", 8) == 0) {
print_line(level, "ReiserFS file system (old 3.5 format, standard journal, starts at %d KiB)", at);
newformat = 0;
} else if (memcmp(buf + 52, "ReIsEr2Fs", 9) == 0) {
print_line(level, "ReiserFS file system (new 3.6 format, standard journal, starts at %d KiB)", at);
newformat = 1;
} else if (memcmp(buf + 52, "ReIsEr3Fs", 9) == 0) {
newformat = get_le_short(buf + 72);
if (newformat == 0) {
print_line(level, "ReiserFS file system (old 3.5 format, non-standard journal, starts at %d KiB)", at);
} else if (newformat == 2) {
print_line(level, "ReiserFS file system (new 3.6 format, non-standard journal, starts at %d KiB)", at);
newformat = 1;
} else {
print_line(level, "ReiserFS file system (v3 magic, but unknown version %d, starts at %d KiB)", newformat, at);
continue;
}
} else
continue;
/* get data */
blockcount = get_le_long(buf);
blocksize = get_le_short(buf + 44);
/* for new format only:
hashtype = get_le_long(buf + 64);
*/
/* get label */
get_string(buf + 100, 16, s);
if (s[0])
print_line(level + 1, "Volume name \"%s\"", s);
format_uuid(buf + 84, s);
print_line(level + 1, "UUID %s", s);
/* print size */
format_blocky_size(s, blockcount, blocksize, "blocks", NULL);
print_line(level + 1, "Volume size %s", s);
/* TODO: print hash code */
}
}
/*
* Reiser4 file system
*/
void detect_reiser4(SECTION *section, int level)
{
unsigned char *buf;
char s[256];
int layout_id;
char layout_name[64];
u4 blocksize;
u8 blockcount;
if (get_buffer(section, 16 * 4096, 1024, (void **)&buf) < 1024)
return;
/* check signature */
if (memcmp(buf, "ReIsEr4", 7) != 0)
return;
/* get data from master superblock */
layout_id = get_le_short(buf + 16);
blocksize = get_le_short(buf + 18);
if (layout_id == 0)
strcpy(layout_name, "4.0 layout");
else
sprintf(layout_name, "Unknown layout with ID %d", layout_id);
format_size(s, blocksize);
print_line(level, "Reiser4 file system (%s, block size %s)",
layout_name, s);
/* get label and UUID */
get_string(buf + 36, 16, s);
if (s[0])
print_line(level + 1, "Volume name \"%s\"", s);
format_uuid(buf + 20, s);
print_line(level + 1, "UUID %s", s);
if (layout_id == 0) {
/* read 4.0 superblock */
if (get_buffer(section, 17 * 4096, 1024, (void **)&buf) < 1024)
return;
if (memcmp(buf + 52, "ReIsEr40FoRmAt", 14) != 0) {
print_line(level + 1, "Superblock for 4.0 format missing");
return;
}
blockcount = get_le_quad(buf);
format_blocky_size(s, blockcount, blocksize, "blocks", NULL);
print_line(level + 1, "Volume size %s", s);
}
}
/*
* Linux RAID persistent superblock
*/
static char *levels[] = {
"Multipath",
"\'HSM\'",
"\'translucent\'",
"Linear",
"RAID0",
"RAID1",
NULL, NULL,
"RAID4(?)",
"RAID5"
};
void detect_linux_raid(SECTION *section, int level)
{
unsigned char *buf;
u8 pos;
int rlevel, nr_disks, raid_disks, spare;
u1 uuid[16];
char s[256];
/* don't do this if:
* - the size is unknown (0)
* - the size is too small for the calculation
* - it is inefficient to read from the end of the source
*/
if (section->size < 65536 || section->source->sequential)
return;
/* get RAID superblock from the end of the device */
pos = (section->size & ~65535) - 65536;
if (get_buffer(section, pos, 4096, (void **)&buf) < 4096)
return;
/* signature */
if (get_le_long(buf) != 0xa92b4efc)
return;
print_line(level, "Linux RAID disk, version %lu.%lu.%lu",
get_le_long(buf + 4), get_le_long(buf + 8),
get_le_long(buf + 12));
/* get some data */
rlevel = (int)(long)get_le_long(buf + 28); /* is signed, actually */
nr_disks = get_le_long(buf + 36);
raid_disks = get_le_long(buf + 40);
spare = nr_disks - raid_disks;
/* find the name for the personality in the table */
if (rlevel < -4 || rlevel > 5 || levels[rlevel+4] == NULL) {
print_line(level + 1, "Unknown RAID level %d using %d regular %d spare disks",
rlevel, raid_disks, spare);
} else {
print_line(level + 1, "%s set using %d regular %d spare disks",
levels[rlevel+4], raid_disks, spare);
}
/* get the UUID */
memcpy(uuid, buf + 5*4, 4);
memcpy(uuid + 4, buf + 13*4, 3*4);
format_uuid(uuid, s);
print_line(level + 1, "RAID set UUID %s", s);
}
/*
* Linux LVM1
*/
void detect_linux_lvm(SECTION *section, int level)
{
unsigned char *buf;
char s[256];
int minor_version, pv_number;
u8 pe_size, pe_count, pe_start;
if (get_buffer(section, 0, 1024, (void **)&buf) < 1024)
return;
/* signature */
if (buf[0] != 'H' || buf[1] != 'M')
return;
/* helpful sanity check... */
if (get_le_long(buf + 36) == 0 || get_le_long(buf + 40) == 0)
return;
minor_version = get_le_short(buf + 2);
print_line(level, "Linux LVM1 volume, version %d%s",
minor_version,
(minor_version < 1 || minor_version > 2) ? " (unknown)" : "");
/* volume group name */
get_string(buf + 172, 128, s);
print_line(level + 1, "Volume group name \"%s\"", s);
/* "UUID" of this physical volume */
format_uuid_lvm(buf + 0x2c, s);
print_line(level + 1, "PV UUID %s", s);
/* number of this physical volume */
pv_number = get_le_long(buf + 432);
print_line(level + 1, "PV number %d", pv_number);
/* volume size */
pe_size = get_le_long(buf + 452);
pe_count = get_le_long(buf + 456);
format_blocky_size(s, pe_count, pe_size * 512, "PEs", NULL);
print_line(level + 1, "Useable size %s", s);
/* get start of first PE */
if (minor_version == 1) {
/* minor format 1: first PE starts after the declared length of the PE tables */
pe_start = get_le_long(buf + 36) + get_le_long(buf + 40);
} else if (minor_version == 2) {
/* minor format 2: a field in the header indicates this */
pe_start = get_le_long(buf + 464) << 9;
} else {
/* unknown minor format */
pe_start = 0;
}
/* try to detect from first PE */
if (pe_start > 0) {
analyze_recursive(section, level + 1,
pe_start, 0, 0);
/* TODO: elaborate on this by reading the PE allocation map */
}
}
/*
* Linux LVM2
*/
void detect_linux_lvm2(SECTION *section, int level)
{
unsigned char *buf;
int at, i;
char s[256];
u8 labelsector;
u4 labeloffset;
u8 pvsize, mdoffset, mdsize;
int mda_version;
for (at = 0; at < 4; at++) {
if (get_buffer(section, at * 512, 512, (void **)&buf) < 512)
continue;
/* check signature */
if (memcmp(buf, "LABELONE", 8) != 0)
continue;
labelsector = get_le_quad(buf + 8);
labeloffset = get_le_long(buf + 20);
if (memcmp(buf + 24, "LVM2 001", 8) != 0) {
get_string(buf + 24, 8, s);
print_line(level, "LABELONE label at sector %d, unknown type \"%s\"",
at, s);
return;
}
print_line(level, "Linux LVM2 volume, version 001");
print_line(level + 1, "LABELONE label at sector %d",
at);
if (labeloffset >= 512 || labelsector > 256 ||
labelsector != at) {
print_line(level + 1, "LABELONE data inconsistent, aborting analysis");
return;
}
/* "UUID" of this physical volume */
format_uuid_lvm(buf + labeloffset, s);
print_line(level + 1, "PV UUID %s", s);
/* raw volume size */
pvsize = get_le_quad(buf + labeloffset + 32);
format_size_verbose(s, pvsize);
print_line(level + 1, "Volume size %s", s);
/* find first metadata area in list */
mdoffset = 0;
for (i = 0; i < 16; i++)
if (get_le_quad(buf + labeloffset + 40 + i * 16) == 0) {
i++;
mdoffset = get_le_quad(buf + labeloffset + 40 + i * 16);
mdsize = get_le_quad(buf + labeloffset + 40 + i * 16 + 8);
break;
}
if (mdoffset == 0)
return;
if (get_buffer(section, mdoffset, mdsize, (void **)&buf) < mdsize)
return;
if (memcmp(buf + 4, " LVM2 x[5A%r0N*>", 16) != 0)
return;
mda_version = get_le_long(buf + 20);
print_line(level + 1, "Meta-data version %d", mda_version);
/* TODO: parse the metadata area (big task...) */
return;
}
}
/*
* Linux swap area
*/
void detect_linux_swap(SECTION *section, int level)
{
int i, en, pagesize;
unsigned char *buf;
u4 version, pages;
char s[256];
int pagesizes[] = { 4096, 8192, 0 };
for (i = 0; pagesizes[i]; i++) {
pagesize = pagesizes[i];
if (get_buffer(section, pagesize - 512, 512, (void **)&buf) != 512)
break; /* assumes page sizes increase through the loop */
if (memcmp((char *)buf + 512 - 10, "SWAP-SPACE", 10) == 0) {
print_line(level, "Linux swap, version 1, %d KiB pages",
pagesize >> 10);
}
if (memcmp((char *)buf + 512 - 10, "SWAPSPACE2", 10) == 0) {
if (get_buffer(section, 1024, 512, (void **)&buf) != 512)
break; /* really shouldn't happen */
for (en = 0; en < 2; en++) {
version = get_ve_long(en, buf);
if (version >= 1 && version < 10)
break;
}
if (en < 2) {
print_line(level, "Linux swap, version 2, subversion %d, %d KiB pages, %s",
(int)version, pagesize >> 10, get_ve_name(en));
if (version == 1) {
pages = get_ve_long(en, buf + 4) - 1;
format_blocky_size(s, pages, pagesize, "pages", NULL);
print_line(level + 1, "Swap size %s", s);
}
} else {
print_line(level, "Linux swap, version 2, illegal subversion, %d KiB pages",
pagesize >> 10);
}
}
}
}
/*
* various file systems
*/
void detect_linux_misc(SECTION *section, int level)
{
int magic, fill, off, en;
unsigned char *buf;
char s[256];
u8 size, blocks, blocksize;
fill = get_buffer(section, 0, 2048, (void **)&buf);
if (fill < 512)
return;
/* minix file system */
if (fill >= 2048) {
int version = 0, namesize = 14;
magic = get_le_short(buf + 1024 + 16);
if (magic == 0x137F)
version = 1;
if (magic == 0x138F) {
version = 1;
namesize = 30;
}
if (magic == 0x2468)
version = 2;
if (magic == 0x2478) {
version = 2;
namesize = 30;
}
if (version) {
print_line(level, "Minix file system (v%d, %d chars)",
version, namesize);
if (version == 1)
blocks = get_le_short(buf + 1024 + 2);
else
blocks = get_le_long(buf + 1024 + 20);
blocks = (blocks - get_le_short(buf + 1024 + 8))
<< get_le_short(buf + 1024 + 10);
format_blocky_size(s, blocks, 1024, "blocks", NULL);
print_line(level + 1, "Volume size %s", s);
}
}
/* Linux romfs */
if (memcmp(buf, "-rom1fs-", 8) == 0) {
size = get_be_long(buf + 8);
print_line(level, "Linux romfs");
print_line(level+1, "Volume name \"%.300s\"", (char *)(buf + 16));
format_size_verbose(s, size);
print_line(level+1, "Volume size %s", s);
}
/* Linux cramfs */
for (off = 0; off <= 512; off += 512) {
if (fill < off + 512)
break;
for (en = 0; en < 2; en++) {
if (get_ve_long(en, buf + off) == 0x28cd3d45) {
print_line(level, "Linux cramfs, starts sector %d, %s",
off >> 9, get_ve_name(en));
get_string(buf + off + 48, 16, s);
print_line(level + 1, "Volume name \"%s\"", s);
size = get_ve_long(en, buf + off + 4);
blocks = get_ve_long(en, buf + off + 40);
format_size_verbose(s, size);
print_line(level + 1, "Compressed size %s", s);
format_blocky_size(s, blocks, 4096, "blocks", " -assumed-");
print_line(level + 1, "Data size %s", s);
}
}
}
/* Linux squashfs */
for (en = 0; en < 2; en++) {
if (get_ve_long(en, buf) == 0x73717368) {
int major, minor;
major = get_ve_short(en, buf + 28);
minor = get_ve_short(en, buf + 30);
print_line(level, "Linux squashfs, version %d.%d, %s",
major, minor, get_ve_name(en));
if (major > 2)
size = get_ve_quad(en, buf + 63);
else
size = get_ve_long(en, buf + 8);
if (major > 1)
blocksize = get_ve_long(en, buf + 51);
else
blocksize = get_ve_short(en, buf + 32);
format_size_verbose(s, size);
print_line(level + 1, "Compressed size %s", s);
format_size(s, blocksize);
print_line(level + 1, "Block size %s", s);
}
}
}
/*
* various boot code signatures
*/
void detect_linux_loader(SECTION *section, int level)
{
int fill, executable, id;
unsigned char *buf;
if (section->flags & FLAG_IN_DISKLABEL)
return;
fill = get_buffer(section, 0, 2048, (void **)&buf);
if (fill < 512)
return;
executable = (get_le_short(buf + 510) == 0xaa55) ? 1 : 0;
/* boot sector stuff */
if (executable && (memcmp(buf + 2, "LILO", 4) == 0 ||
memcmp(buf + 6, "LILO", 4) == 0))
print_line(level, "LILO boot loader");
if (executable && memcmp(buf + 3, "SYSLINUX", 8) == 0)
print_line(level, "SYSLINUX boot loader");
if (fill >= 1024 && find_memory(buf, fill, "ISOLINUX", 8) >= 0)
print_line(level, "ISOLINUX boot loader");
/* we know GRUB a little better... */
if (executable &&
find_memory(buf, 512, "Geom\0Hard Disk\0Read\0 Error\0", 27) >= 0) {
if (buf[0x3e] == 3) {
print_line(level, "GRUB boot loader, compat version %d.%d, boot drive 0x%02x",
(int)buf[0x3e], (int)buf[0x3f], (int)buf[0x40]);
} else if (executable && buf[0x1bc] == 2 && buf[0x1bd] <= 2) {
id = buf[0x3e];
if (id == 0x10) {
print_line(level, "GRUB boot loader, compat version %d.%d, normal version",
(int)buf[0x1bc], (int)buf[0x1bd]);
} else if (id == 0x20) {
print_line(level, "GRUB boot loader, compat version %d.%d, LBA version",
(int)buf[0x1bc], (int)buf[0x1bd]);
} else {
print_line(level, "GRUB boot loader, compat version %d.%d",
(int)buf[0x1bc], (int)buf[0x1bd]);
}
} else {
print_line(level, "GRUB boot loader, unknown compat version %d",
buf[0x3e]);
}
}
/* Linux kernel loader */
if (fill >= 1024 && memcmp(buf + 512 + 2, "HdrS", 4) == 0) {
print_line(level, "Linux kernel build-in loader");
}
/* Debian install floppy splitter */
/* (not exactly boot code, but should be detected before gzip/tar) */
if (memcmp(buf, "Floppy split ", 13) == 0) {
char *name = (char *)buf + 32;
char *number = (char *)buf + 164;
char *total = (char *)buf + 172;
print_line(level, "Debian floppy split, name \"%s\", disk %s of %s",
name, number, total);
}
}
/* EOF */
syntax highlighted by Code2HTML, v. 0.9.1