#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/disk.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOMedia.h>
#include <MediaKit/GPTTypes.h>
#include <IOKit/IOBSD.h>
#include <IOKit/IOTypes.h>
#include "lf_cs_checksum.h"
#include "lf_cs_disk_format.h"
#include "lf_cs_logging.h"
#include "lf_cs.h"
#define VALUE_UNSPECIFIED ((uint64_t)-1)
static unsigned
cs_verify_cksum(cksum_alg_t alg, const void *ptr, size_t len,
uint8_t *chk)
{
uint8_t c[MAX_CKSUM_NBYTES];
if (!(alg == CKSUM_NONE) && !(alg == CKSUM_ALG_CRC_32)) {
return CS_STATUS_INVALID;
}
cksum_init(alg, c);
if (chk == NULL) {
cksum(alg, (chk = (uint8_t *)ptr) + MAX_CKSUM_NBYTES,
len - MAX_CKSUM_NBYTES, c);
} else {
cksum(alg, ptr, len, c);
}
if (memcmp(c, chk, MAX_CKSUM_NBYTES)) {
return CS_STATUS_CKSUM;
}
return CS_STATUS_OK;
}
#define CS_CHKPOINT_BLK(_b) \
((_b)->mh_blk_type >= BLK_TYPE_VOL_HEADER &&\
(_b)->mh_blk_type <= BLK_TYPE_SUT)
#define CS_DSDLIST_BLK(_b) \
((_b)->mh_blk_type == BLK_TYPE_LV_FAMILY_SUPERBLOCK ||\
(_b)->mh_blk_type == BLK_TYPE_LV_FAMILY_XATTR)
static unsigned
cs_verify_blkhdr(const metadata_header_t *hdr, uint16_t alg,
uint8_t type, uint8_t subtype, uint64_t vaddr,
uint64_t laddr, uint64_t txg, uint32_t blksz)
{
unsigned status;
if (alg != CKSUM_NONE && (status = cs_verify_cksum((cksum_alg_t)alg,
hdr, blksz, NULL)) != CS_STATUS_OK) {
return status;
}
if (type != BLK_TYPE_UNKNOWN && hdr->mh_blk_type != type) {
return CS_STATUS_BLKTYPE;
}
if (subtype != BLK_SUBTYPE_UNKNOWN && hdr->mh_blk_subtype != subtype) {
return CS_STATUS_INVALID;
}
if (vaddr != VALUE_UNSPECIFIED && hdr->mh_vaddr != vaddr) {
return CS_STATUS_ADDRESS;
}
if (laddr != VALUE_UNSPECIFIED && hdr->mh_laddr != laddr) {
return CS_STATUS_ADDRESS;
}
if (txg != VALUE_UNSPECIFIED) {
if (CS_CHKPOINT_BLK(hdr) && hdr->mh_txg_id != txg)
return CS_STATUS_TXG;
else if (hdr->mh_txg_id > txg)
return CS_STATUS_TXG;
}
if (blksz != 0 && hdr->mh_blk_size != blksz) {
return CS_STATUS_INVALID;
}
if (!CS_DSDLIST_BLK(hdr) &&
(hdr->mh_blk_flags & BLK_FLAG_IN_DSD_LIST)) {
return CS_STATUS_INVALID;
}
return CS_STATUS_OK;
}
static unsigned
cs_verify_versions(const metadata_header_t *hdr)
{
if (hdr != NULL && hdr->mh_format_version !=
CORESTORAGE_FORMAT_VERSION) {
return CS_INFO_VERSIONITIS | CS_STATUS_NOTCS;
}
if (hdr != NULL &&
hdr->mh_blk_type == BLK_TYPE_VOL_HEADER &&
((dk_vol_header_t *)hdr)->vh_endianness !=
BYTE_ORDER_MARK) {
return CS_INFO_VERSIONITIS | CS_STATUS_NOTCS;
}
return CS_STATUS_OK;
}
static unsigned
cs_verify_vh(dk_vol_header_t *hdr, uint32_t blksz)
{
unsigned status;
status = cs_verify_blkhdr(&hdr->vh_header, hdr->vh_cksum_alg,
BLK_TYPE_VOL_HEADER,
BLK_SUBTYPE_NO_SUBTYPE, 0, 0,
VALUE_UNSPECIFIED, VOL_HEADER_NBYTES);
if (status != CS_STATUS_OK) {
return status;
}
status = cs_verify_versions(&hdr->vh_header);
if (status != CS_STATUS_OK) {
return status;
}
if (!hdr->vh_num_labels || hdr->vh_num_labels > MAX_DISK_LABELS) {
return CS_STATUS_INVALID;
}
if (hdr->vh_label_max_nbytes % blksz != 0) {
return CS_STATUS_INVALID;
}
if (hdr->vh_blksz == 0 || hdr->vh_blksz % blksz != 0) {
return CS_STATUS_INVALID;
}
if (hdr->vh_pv_nbytes % blksz != 0 ||
hdr->vh_pv_nbytes < CS_ALIGN(VOL_HEADER_NBYTES,
blksz, true) * NUM_VOL_HEADERS) {
return CS_STATUS_INVALID;
}
if (hdr->vh_pv_resize != 0 && (hdr->vh_pv_resize % blksz != 0 ||
hdr->vh_pv_resize < CS_ALIGN(VOL_HEADER_NBYTES,
blksz, true) * NUM_VOL_HEADERS)) {
return CS_STATUS_INVALID;
}
if (hdr->vh_old_pv_nbytes != 0 &&
(hdr->vh_old_pv_nbytes % blksz != 0 ||
hdr->vh_old_pv_nbytes < CS_ALIGN(VOL_HEADER_NBYTES,
blksz, true) * NUM_VOL_HEADERS)) {
return CS_STATUS_INVALID;
}
return CS_STATUS_OK;
}
static bool
cs_get_content_hint_for_pv(struct stat *st)
{
bool has_cs_hint;
int dev_major, dev_minor;
CFMutableDictionaryRef matching;
has_cs_hint = false;
if ((matching = IOServiceMatching(kIOMediaClass))) {
CFTypeRef str;
io_service_t service;
if ((matching = IOServiceMatching(kIOMediaClass))) {
CFNumberRef num_ref;
dev_major = major(st->st_rdev);
dev_minor = minor(st->st_rdev);
num_ref = CFNumberCreate(kCFAllocatorDefault,
kCFNumberIntType, &dev_major);
if (num_ref) {
CFDictionarySetValue(matching,
CFSTR(kIOBSDMajorKey),
num_ref);
CFRelease(num_ref);
}
num_ref = CFNumberCreate(kCFAllocatorDefault,
kCFNumberIntType, &dev_minor);
if (num_ref) {
CFDictionarySetValue(matching,
CFSTR(kIOBSDMinorKey),
num_ref);
CFRelease(num_ref);
}
service = IOServiceGetMatchingService(
kIOMasterPortDefault, matching);
if (!service) {
goto out;
}
if ((str = IORegistryEntryCreateCFProperty( service,
CFSTR(kIOMediaContentHintKey),
kCFAllocatorDefault, 0)) !=
NULL) {
has_cs_hint = CFStringCompare((CFStringRef)str,
CFSTR(APPLE_CORESTORAGE_UUID), 0) ==
kCFCompareEqualTo;
CFRelease(str);
IOObjectRelease(service);
}
}
}
out:
return has_cs_hint;
}
static unsigned
cs_verify_hdrfields(void *block, uint16_t alg, uint8_t type,
uint8_t subtype, uint64_t vaddr, uint64_t laddr,
uint32_t blksz)
{
unsigned status;
if (alg != CKSUM_NONE && (status = cs_verify_cksum((cksum_alg_t)alg,
block, blksz, NULL)) !=
CS_STATUS_OK) {
return status;
}
if (type != BLK_TYPE_VOL_HEADER) {
return CS_STATUS_BLKTYPE;
}
if (subtype != BLK_SUBTYPE_NO_SUBTYPE) {
return CS_STATUS_INVALID;
}
if (vaddr != 0) {
return CS_STATUS_ADDRESS;
}
if (laddr != 0) {
return CS_STATUS_ADDRESS;
}
if (blksz != VOL_HEADER_NBYTES) {
return CS_STATUS_INVALID;
}
return CS_STATUS_OK;
}
static unsigned
cs_older_cs_version(dk_vol_header_t *hdr, unsigned status)
{
uint16_t cksum_alg;
struct {
uint8_t zeroes[VOL_HEADER_NBYTES];
} inprogress = {0};
struct v11_volhdr {
uint8_t mh_cksum[MAX_CKSUM_NBYTES];
uint16_t mh_format_version;
uint8_t mh_blk_type;
uint8_t mh_blk_subtype;
uint32_t mh_bundle_version;
uint64_t mh_txg_id;
uint64_t mh_vaddr;
uint64_t mh_laddr;
uint64_t mh_blk_owner;
uint32_t mh_blk_size;
uint8_t mh_blk_flags;
uint8_t mh_reserved1;
uint16_t mh_reserved2;
uint64_t mh_reserved8;
uint64_t vh_pv_nbytes;
uint64_t vh_pv_resize;
uint64_t vh_old_pv_nbytes;
uint16_t vh_endianness;
uint16_t vh_cksum_alg;
uint16_t vh_reserved2;
uint16_t vh_num_labels;
uint32_t vh_blksz;
uint32_t vh_label_max_nbytes;
uint64_t vh_label_addr[MAX_DISK_LABELS];
uint64_t vh_move_label_addr[MAX_DISK_LABELS];
uint16_t vh_wipe_key_nbytes[2];
uint16_t vh_wipe_key_alg[2];
uint8_t vh_wipe_key[2][MAX_WIPEKEY_NBYTES];
uint8_t vh_pv_uuid[16];
uint8_t vh_lvg_uuid[16];
} *v11;
if ((CS_STATUS(status) != CS_STATUS_OK) &&
(hdr->vh_header.mh_format_version !=
CORESTORAGE_FORMAT_VERSION)) {
if (!memcmp(hdr, &inprogress, VOL_HEADER_NBYTES)) {
return CS_INFO_ZERO_VH | CS_STATUS_NOTCS;
}
v11 = (struct v11_volhdr *)hdr;
if (v11->mh_format_version == 11) {
cksum_alg = v11->vh_cksum_alg;
if (cs_verify_hdrfields(v11, cksum_alg,
v11->mh_blk_type,
v11->mh_blk_subtype,
v11->mh_vaddr,
v11->mh_laddr,
v11->mh_blk_size) ==
CS_STATUS_OK) {
return CS_INFO_VERSIONITIS | CS_STATUS_NOTCS;
}
}
}
return status;
}
static int
cs_fd_is_corestorage_pv(int disk_fd, bool *is_cs_pv)
{
struct stat st;
bool pv_has_csuuid_hint;
int vh_idx, error;
uint32_t pv_blksz;
uint64_t pv_nblks;
uint64_t offset[NUM_VOL_HEADERS + 1];
unsigned status[NUM_VOL_HEADERS + 1];
dk_vol_header_t hdr[NUM_VOL_HEADERS + 1];
error = 0;
pv_blksz = 0;
pv_nblks = 0;
*is_cs_pv = false;
pv_has_csuuid_hint = false;
infomsg("Tasting Apple_CoreStorage plugin, fd: %d\n", disk_fd);
fstat(disk_fd, &st);
if (!S_ISBLK(st.st_mode)) {
errmsg("Apple_CoreStorage plugin only supports block "
"device. Aborting taste.\n");
return ENOTSUP;
}
if (ioctl(disk_fd, DKIOCGETBLOCKSIZE, &pv_blksz) == -1) {
error = errno;
}
if (!error && ioctl(disk_fd, DKIOCGETBLOCKCOUNT, &pv_nblks) == -1) {
error = errno;
}
if (!error && (!pv_blksz || !pv_nblks)) {
error = ENOTBLK;
}
if (error) {
errmsg("Failed to get blocksize and block count "
"for the device. Aborting taste.\n");
return error;
}
pv_has_csuuid_hint = cs_get_content_hint_for_pv(&st);
if (!pv_has_csuuid_hint) {
*is_cs_pv = false;
return 0;
}
offset[0] = 0;
offset[1] = pv_nblks * pv_blksz - VOL_HEADER_NBYTES;
for (vh_idx = 0; vh_idx < NUM_VOL_HEADERS; ++vh_idx ) {
status[vh_idx] = CS_STATUS_INVALID;
}
for (vh_idx = 0; vh_idx < NUM_VOL_HEADERS; ++vh_idx) {
ssize_t bytes_read;
bytes_read = pread(disk_fd, &hdr[vh_idx], VOL_HEADER_NBYTES,
offset[vh_idx]);
if (bytes_read == -1) {
error = errno;
}
if (!error && bytes_read != VOL_HEADER_NBYTES) {
error = EIO;
}
if (error) {
errmsg("Failed to read volume-hearder at offset %llu "
"for disk with fd %d, Aborting taste.\n",
offset[vh_idx], disk_fd);
break;
}
status[vh_idx] = cs_verify_vh(&hdr[vh_idx], pv_blksz);
if (CS_STATUS(status[vh_idx]) != CS_STATUS_OK) {
status[vh_idx] = cs_older_cs_version(&hdr[vh_idx],
status[vh_idx]);
if (CS_INFO(status[vh_idx]) & CS_INFO_VERSIONITIS) {
infomsg("Disk with fd %u has older physical "
"volume header format.\n",
disk_fd);
status[vh_idx] = CS_STATUS_OK;
}
}
if (status[vh_idx] != CS_STATUS_OK) {
break;
}
}
if (error) {
return error;
}
for (vh_idx = 0; vh_idx < NUM_VOL_HEADERS; ++vh_idx ) {
if (status[vh_idx] != CS_STATUS_OK) {
break;
}
*is_cs_pv = true;
}
return 0;
}
static int
cs_uvfsop_taste(int disk_fd)
{
int error;
bool is_cs_pv;
error = cs_fd_is_corestorage_pv(disk_fd, &is_cs_pv);
if (error) {
errmsg("Encountered error while tasting disk with file "
"descriptor %d for Apple_CoreStorage "
"plugin (error %d).\n", disk_fd, error);
return error;
}
if (!is_cs_pv) {
errmsg("Disk with file descriptor %d is not an corestorage "
"physical volume.\n", disk_fd);
return ENOTSUP;
}
infomsg("Disk with file descriptor %d is corestorage physical "
"volume.\n", disk_fd);
return 0;
}
static int
cs_uvfsop_init(void)
{
infomsg("Initializing CS UserFS plugin...\n");
return 0;
}
static void
cs_uvfsop_fini(void)
{
infomsg("Cleaning up CS UserFS plugin...\n");
}
UVFSFSOps cs_fsops = {
.fsops_version = UVFS_FSOPS_VERSION_CURRENT,
.fsops_init = cs_uvfsop_init,
.fsops_fini = cs_uvfsop_fini,
.fsops_taste = cs_uvfsop_taste,
};
__attribute__((visibility("default")))
void
livefiles_plugin_init(UVFSFSOps **ops)
{
if (ops) {
*ops = &cs_fsops;
}
}