Files
s390-tools/zdump/ngdump.c
Alexander Egorenkov 6c6938d1e2 zdump/ngdump: Use OpenSSL's SHA256 to compute digest of dump image
Replace the popen() call to the external tool sha256sum with OpenSSL's
SHA256 digest algorithm to compute the checksum of a dump image
to improve security of NGDump.

The call to popen() is inherently insecure because a malicious user
controlling a dump device could manipulate the latter and let the user
applying zgetdump to the dump device execute arbitrary shell code.

Furthermore, getting rid of process and shell spawning performed by
popen() shall improve performance of calculation of dump image
checksums.

Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00

611 lines
16 KiB
C

/*
* zgetdump - Tool for copying and converting System z dumps
*
* NGDump dump tool
*
* Copyright IBM Corp. 2021
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <limits.h>
#include <unistd.h>
#include <sys/mount.h>
#include <openssl/evp.h>
#include "lib/dasd_base.h"
#include "lib/util_libc.h"
#include "lib/util_part.h"
#include "lib/util_log.h"
#include "lib/vtoc.h"
#include "boot/boot_defs.h"
#include "boot/linux_layout.h"
#include "zg.h"
#include "ngdump.h"
#define NGDUMP_META_VERSION 1
#define NGDUMP_META_FILENAME "ngdump.meta"
static const char *const ngtype2str[] = {
[NG_TYPE_DASD] = "DASD",
[NG_TYPE_NVME] = "NVME"
};
static int read_meta_from_file(const char *filename, struct ngdump_meta *meta)
{
FILE *fp = NULL;
char *line = NULL;
util_log_print(UTIL_LOG_TRACE,
"%s: Reading meta file \"%s\"\n",
__func__, filename);
memset(meta, 0, sizeof(*meta));
fp = fopen(filename, "r");
if (!fp) {
util_log_print(UTIL_LOG_TRACE,
"%s: Could not open \"%s\" (%s)\n",
__func__, filename, strerror(errno));
return -1;
}
while (fscanf(fp, "%m[^\n]\n", &line) == 1) {
char *ptr, *param = NULL, *value = NULL;
/* Skip comments and empty lines */
ptr = util_strstrip(line);
if (strlen(ptr) == 0 || ptr[0] == '#')
goto next_line;
int n = sscanf(ptr, "%m[^=\n]=%m[^\n]\n", &param, &value);
if (n != 2)
goto next_line;
if (strcmp(param, "version") == 0) {
char *endptr;
int version;
version = strtol(value, &endptr, 0);
if (*endptr == '\0')
meta->version = version;
} else if (strcmp(param, "file") == 0) {
meta->file = value;
value = NULL;
} else if (strcmp(param, "sha256sum") == 0) {
meta->sha256sum = value;
value = NULL;
}
next_line:
free(param);
free(value);
free(line);
line = NULL;
}
fclose(fp);
return 0;
}
static int calc_sha256sum(const char *filename, char **cksum)
{
const unsigned long buflen = PAGE_SIZE;
const EVP_MD *digtype = EVP_sha256();
const size_t diglen = EVP_MD_size(digtype);
unsigned char *digest;
EVP_MD_CTX *ctx = NULL;
size_t i;
FILE *fp = NULL;
int n, rc;
void *buf;
buf = util_malloc(buflen);
digest = util_malloc(diglen);
fp = fopen(filename, "r");
if (!fp) {
util_log_print(UTIL_LOG_ERROR,
"%s: Could not open \"%s\" (%s)\n",
__func__, filename, strerror(errno));
rc = -1;
goto out;
}
ctx = EVP_MD_CTX_new();
if (!ctx) {
rc = -1;
goto out;
}
rc = EVP_DigestInit_ex(ctx, digtype, NULL);
if (!rc) {
rc = -1;
goto out;
}
while (!feof(fp)) {
n = fread(buf, 1, buflen, fp);
if (ferror(fp)) {
util_log_print(UTIL_LOG_ERROR,
"%s: Could not read file \"%s\" (%s)\n",
__func__, filename, strerror(errno));
rc = -1;
goto out;
}
rc = EVP_DigestUpdate(ctx, buf, n);
if (!rc) {
rc = -1;
goto out;
}
}
rc = EVP_DigestFinal(ctx, digest, NULL);
if (!rc) {
rc = -1;
goto out;
}
/* Two characters for each digest byte and
additional one for string terminator */
*cksum = util_malloc(diglen * 2 + 1);
for (i = 0; i < diglen; i++)
sprintf(*cksum + 2 * i, "%02x", digest[i]);
util_log_print(UTIL_LOG_DEBUG,
"%s: Dump checksum %s\n",
__func__, *cksum);
rc = 0;
out:
free(digest);
free(buf);
if (fp)
fclose(fp);
if (ctx)
EVP_MD_CTX_free(ctx);
return rc;
}
static int check_sha256sum(const char *filename, const char *expected_cksum)
{
char *got_cksum = NULL;
int rc;
rc = calc_sha256sum(filename, &got_cksum);
if (rc)
goto out_free_cksum;
rc = strcmp(expected_cksum, got_cksum);
if (rc)
warnx("Invalid dump file SHA256 checksum, expected %s, got %s",
expected_cksum, got_cksum);
out_free_cksum:
free(got_cksum);
return rc;
}
static int validate_meta(const char *mount_point, struct ngdump_meta *meta)
{
char *filename = NULL;
int rc;
if (meta->version != NGDUMP_META_VERSION) {
warnx("Invalid NGDump version");
return -1;
}
/*
* File might be not set if no dump was made yet after preparing
* with zipl, therefore, it is not considered an error. It indicates
* that the given partition is a valid NGDump partition but with no
* dump present.
*/
if (!meta->file) {
util_log_print(UTIL_LOG_TRACE,
"%s: No dump file present\n", __func__);
return 0;
}
if (!meta->sha256sum) {
warnx("Invalid NGDump SHA256 checksum");
return -1;
}
util_asprintf(&filename, "%s/%s", mount_point, meta->file);
rc = access(filename, R_OK);
if (rc) {
warnx("Could not access dump file \"%s\"", meta->file);
goto out;
}
rc = check_sha256sum(filename, meta->sha256sum);
if (rc)
goto out;
rc = 0;
out:
free(filename);
return rc;
}
int ngdump_read_meta_from_device(const char *device, struct ngdump_meta *meta)
{
char mount_point[] = "/tmp/zdump-ngdump-XXXXXX";
char *filename = NULL;
int rc = 0;
/* Create a mount point directory */
if (mkdtemp(mount_point) == NULL) {
warnx("Could not create directory \"%s\"", mount_point);
rc = -1;
goto out;
}
rc = mount(device, mount_point, NGDUMP_FSTYPE, MS_RDONLY, NULL);
if (rc) {
util_log_print(UTIL_LOG_TRACE,
"%s: Could not mount \"%s\" (%s)\n",
__func__, device, strerror(errno));
goto out_rmdir;
}
util_asprintf(&filename, "%s/%s", mount_point, NGDUMP_META_FILENAME);
rc = read_meta_from_file(filename, meta);
free(filename);
if (rc)
goto out_umount;
rc = validate_meta(mount_point, meta);
if (rc)
goto out_umount;
rc = 0;
out_umount:
umount(mount_point);
out_rmdir:
rmdir(mount_point);
out:
return rc;
}
/*
* Convert disk blockpointer to the offset in blocks.
* Use eckd blockpointer format if hd_geometry is provided, otherwise linear blockpointer.
* Return u64(-1) in case the blockpointer contains zeroes.
*/
static uint64_t blockptr2blk(union disk_blockptr *ptr, const struct hd_geometry *geo)
{
uint64_t blk;
/* For NVMe or SCSI use linear blockpointer format. */
/* For DASD use eckd blockpointer format. */
if (!geo) {
blk = ptr->linear.blockno;
if (blk == 0)
return U64_MAX;
} else {
if (ptr->eckd.sec == 0)
return U64_MAX;
blk = ptr->eckd.cyl * geo->heads + ptr->eckd.head; /* Track number */
blk *= geo->sectors; /* Track offset in records */
blk += ptr->eckd.sec - 1; /* Record offset (skipping R0) */
}
return blk;
}
/*
* Based on the provided program table blockpointer find kernel image Boot Map Section
* for dump Boot Map Script. Read the first data blockpointer from the section into
* blockptr area.
*/
static int get_bootmap_dump_image_blkptr(struct zg_fh *zg_fh, union disk_blockptr *program_table,
struct hd_geometry *geo, int blk_size,
union disk_blockptr *blockptr)
{
struct component_entry comp_entry;
struct component_header comp_hdr;
int i, max_entries;
uint64_t blk;
/* Read Boot Map Table and check its magic */
blk = blockptr2blk(program_table, geo);
if (blk == U64_MAX)
return -1;
util_log_print(UTIL_LOG_TRACE,
"%s: Reading program table at offset 0x%016lx\n",
__func__, blk * blk_size);
zg_seek(zg_fh, blk * blk_size, ZG_CHECK);
zg_read(zg_fh, blockptr, sizeof(*blockptr), ZG_CHECK);
if (memcmp((const char *)blockptr, ZIPL_MAGIC, ZIPL_MAGIC_SIZE))
return -1;
/* Read Boot Map Script Pointer 0 */
zg_read(zg_fh, blockptr, sizeof(*blockptr), ZG_CHECK);
blk = blockptr2blk(blockptr, geo);
if (blk == U64_MAX)
return -1;
/* Read 1st Boot Map Script, check its magic and type */
util_log_print(UTIL_LOG_TRACE,
"%s: Reading component header at offset 0x%016lx\n",
__func__, blk * blk_size);
zg_seek(zg_fh, blk * blk_size, ZG_CHECK_ERR);
zg_read(zg_fh, &comp_hdr, sizeof(comp_hdr), ZG_CHECK_ERR);
if (memcmp(comp_hdr.magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE))
return -1;
/* We want only dump script */
if (comp_hdr.type != COMPONENT_HEADER_DUMP)
return -1;
/* Find kernel's Boot Map Section Pointer */
max_entries = (blk_size - sizeof(comp_hdr)) / sizeof(comp_entry);
for (i = 0; i < max_entries; i++) {
zg_read(zg_fh, &comp_entry, sizeof(comp_entry), ZG_CHECK_ERR);
util_log_print(UTIL_LOG_TRACE,
"%s: Component type 0x%x load address 0x%016lx\n",
__func__, comp_entry.type,
comp_entry.compdat.load_address);
/* Is this a kernel image ? */
if (comp_entry.type == COMPONENT_TYPE_LOAD &&
comp_entry.compdat.load_address == IMAGE_ENTRY)
break;
}
if (i >= max_entries) {
util_log_print(UTIL_LOG_DEBUG,
"%s: Couldn't find kernel component\n", __func__);
return -1;
}
/* Read 1st Boot Map Data Pointer in kernel's Boot Map Section */
blk = blockptr2blk((union disk_blockptr *)comp_entry.data, geo);
if (blk == U64_MAX)
return -1;
util_log_print(UTIL_LOG_TRACE,
"%s: Reading component block pointer at offset 0x%016lx\n",
__func__, blk * blk_size);
zg_seek(zg_fh, blk * blk_size, ZG_CHECK_ERR);
zg_read(zg_fh, blockptr, sizeof(*blockptr), ZG_CHECK);
return 0;
}
/*
* This function parses the bootloader program table stored on the nvme device
* and returns the partition index where the dumper's kernel image is stored.
*/
static int get_nvme_part_num(struct zg_fh *zg_fh)
{
union disk_blockptr dump_image_blkptr;
int blk_size, part_num, part_ext;
struct linear_blockptr *blockptr;
struct scsi_mbr mbr;
if (zg_ioctl(zg_fh, BLKSSZGET, &blk_size, "BLKSSZGET", ZG_CHECK_NONE))
return -1;
util_log_print(UTIL_LOG_TRACE, "%s: Block size %d\n",
__func__, blk_size);
/* Read Master Boot Record (MBR) and check its magic */
zg_read(zg_fh, &mbr, sizeof(mbr), ZG_CHECK);
if (memcmp(mbr.magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE))
return -1;
blockptr = &mbr.program_table_pointer;
/*
* Cast linear_blockptr to disk_blockptr before passing it to the function.
* Since no hd_geometry provided, it will be treated as linear_blockptr later on.
*/
if (get_bootmap_dump_image_blkptr(zg_fh, (union disk_blockptr *)blockptr,
NULL, blk_size, &dump_image_blkptr))
return -1;
blockptr = &dump_image_blkptr.linear;
util_log_print(UTIL_LOG_TRACE,
"%s: Component block address 0x%016lx block count %d\n",
__func__, blockptr->blockno,
blockptr->blockct);
if (blockptr->blockno == 0)
return -1;
part_num = util_part_search_fh(zg_fh->fh, blockptr->blockno, blockptr->blockct,
blk_size, &part_ext);
return part_num;
}
/*
* This function scans a VTOC record of cdl formatted DASD to identify a partition
* the specified blockno (0-indexed) belongs to.
*/
static int find_vol1_cdl_part_fh(struct zg_fh *zg_fh, uint64_t blockno, int blk_size,
cchhb_t *vtoc, struct hd_geometry *geo)
{
unsigned int part_count, i;
struct format1_label f1;
uint64_t blk;
blk = cchhb2blk(vtoc, geo);
if (!blk)
return -1;
/* Get actual offset in blocks (special record zero accounted) */
blk--;
part_count = 0;
for (i = 0; i < MAX_VTOC_ENTRIES; i++) {
zg_seek(zg_fh, blk * blk_size, ZG_CHECK);
zg_read(zg_fh, &f1, sizeof(f1), ZG_CHECK_ERR);
/* Skip FMT4 / FMT5 / FMT7 / FMT9 labels */
if (f1.DS1FMTID == 0xf4 ||
f1.DS1FMTID == 0xf5 ||
f1.DS1FMTID == 0xf7 ||
f1.DS1FMTID == 0xf9) {
blk++;
continue;
}
/* only FMT1 and FMT8 labels valid at this point */
if (f1.DS1FMTID != 0xf1 &&
f1.DS1FMTID != 0xf8)
break;
/* OK, we got valid partition data. Check for partition boundaries */
if (blockno >= cchh2blk(&f1.DS1EXT1.llimit, geo) &&
blockno < cchh2blk(&f1.DS1EXT1.ulimit, geo) + geo->sectors) {
return part_count + 1;
}
part_count++;
blk++;
}
/* No matching partition found */
return -1;
}
/*
* This function parses the bootloader program table stored on the eckd device
* and returns the partition index where the dumper's kernel image is stored.
*/
static int get_eckd_part_num(struct zg_fh *zg_fh)
{
union disk_blockptr dump_image_blkptr;
struct eckd_blockptr *blockptr;
struct eckd_boot_record br;
struct vol_label_cdl vl;
struct hd_geometry geo;
uint64_t blk, off;
cchhb_t *vtoc;
int blk_size;
if (zg_ioctl(zg_fh, BLKSSZGET, &blk_size, "BLKSSZGET", ZG_CHECK_NONE))
return -1;
util_log_print(UTIL_LOG_TRACE, "%s: Block size %d\n",
__func__, blk_size);
/* Obtain DASD geometry */
if (dasd_get_geo(zg_fh->path, &geo) != 0)
return -1;
util_log_print(UTIL_LOG_TRACE,
"%s: DASD geometry: cyl=%d, heads=%d, sect=%d\n",
__func__, geo.cylinders, geo.heads, geo.sectors);
/* Read a volume label from CDL-formatted DASD */
off = 2 * blk_size;
util_log_print(UTIL_LOG_TRACE,
"%s: Reading a volume label at offset 0x%016lx\n",
__func__, off);
zg_seek(zg_fh, off, ZG_CHECK_ERR);
zg_read(zg_fh, &vl, sizeof(vl), ZG_CHECK);
/* Verify that we have a VOL1 label */
if (!is_vol1(vl.vollbl))
return -1;
/* Read Master Boot Record and check its magic */
if (vl.br.cc == 0x4040 && vl.br.hh == 0x4040 &&
vl.br.b == 0x40) {
util_log_print(UTIL_LOG_TRACE,
"%s: No boot record pointer found in volume label\n",
__func__);
return -1;
}
blk = cchhb2blk(&vl.br, &geo);
if (blk == 0)
return -1;
off = (blk - 1) * blk_size;
util_log_print(UTIL_LOG_TRACE,
"%s: Reading Master Boot Record at offset 0x%016lx\n",
__func__, off);
zg_seek(zg_fh, off, ZG_CHECK_ERR);
zg_read(zg_fh, &br, sizeof(br), ZG_CHECK);
if (memcmp(br.magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE))
return -1;
blockptr = (struct eckd_blockptr *)&br.program_table_pointer;
/*
* Cast eckd_blockptr to disk_blockptr before passing it to the function.
* With hd_geometry provided, it will be treated as eckd_blockptr later on.
*/
if (get_bootmap_dump_image_blkptr(zg_fh, (union disk_blockptr *)blockptr,
&geo, blk_size, &dump_image_blkptr))
return -1;
blk = blockptr2blk(&dump_image_blkptr, &geo);
if (blk == U64_MAX)
return -1;
util_log_print(UTIL_LOG_TRACE,
"%s: Segment0 block number 0x%016lx\n",
__func__, blk);
vtoc = &((volume_label_t *)&vl)->vtoc;
return find_vol1_cdl_part_fh(zg_fh, blk, blk_size, vtoc, &geo);
}
/*
* This function composes the absolute partition device node name
* based on the device name, device type and the partition number.
*/
int ngdump_get_part_path(const char *disk_path, int part_num,
enum ngdump_disk_type ng_type, char **part_path)
{
char *real_path;
util_log_print(UTIL_LOG_TRACE, "%s: Disk path %s; disk type: %s\n",
__func__, disk_path, ngtype2str[ng_type]);
real_path = util_malloc(PATH_MAX);
if (!realpath(disk_path, real_path)) {
free(real_path);
return -1;
}
util_log_print(UTIL_LOG_TRACE, "%s: Real disk path %s\n",
__func__, real_path);
*part_path = NULL;
switch (ng_type) {
case NG_TYPE_DASD:
util_asprintf(part_path, "%s%d", real_path, part_num);
break;
case NG_TYPE_NVME:
util_asprintf(part_path, "%sp%d", real_path, part_num);
break;
default: /* Unknown type, bail out */
free(real_path);
return -1;
}
free(real_path);
util_log_print(UTIL_LOG_TRACE, "%s: Disk partition path %s\n",
__func__, *part_path);
return 0;
}
/*
* This function checks for the ngdump device type in order to parse the
* bootloader program table and identify the partition where the dumper's
* kernel image is stored.
* part_path is set to the absolute partition device node name and
* the partition number is returned (or -1 when no partition found).
*/
int ngdump_get_dump_part(struct zg_fh *zg_fh, char **part_path)
{
dasd_information2_t dasd_info;
enum ngdump_disk_type ng_type;
int part_num;
if (dasd_get_info(zg_fh->path, &dasd_info) == 0) {
ng_type = NG_TYPE_DASD;
part_num = get_eckd_part_num(zg_fh);
} else {
ng_type = NG_TYPE_NVME;
part_num = get_nvme_part_num(zg_fh);
}
if (part_num <= 0 ||
ngdump_get_part_path(zg_fh->path, part_num, ng_type, part_path) < 0)
return -1;
return part_num;
}