Files
qdl/vip.c
Steve Moskovchenko 6eeb866b15 vip: Fix integer underflow in digest count
If no table(s) of digests have been found, we'll hit an
integer underflow while trying to clean up n-1 table
entries. Fix that.

Signed-off-by: Steve Moskovchenko <stevemo@skydio.com>
Signed-off-by: Jerry Zhang <Jerry@skydio.com>
2025-12-18 18:52:57 -08:00

510 lines
12 KiB
C

// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "sim.h"
#define DIGEST_FULL_TABLE_FILE "DIGEST_TABLE.bin"
#define CHAINED_TABLE_FILE_PREF "ChainedTableOfDigests"
#define CHAINED_TABLE_FILE_MAX_NAME 64
#define DIGEST_TABLE_TO_SIGN_FILE "DigestsToSign.bin"
#define DIGEST_TABLE_TO_SIGN_FILE_MBN (DIGEST_TABLE_TO_SIGN_FILE ".mbn")
#define MAX_DIGESTS_PER_SIGNED_FILE 54
#define MAX_DIGESTS_PER_SIGNED_TABLE (MAX_DIGESTS_PER_SIGNED_FILE - 1)
#define MAX_DIGESTS_PER_CHAINED_FILE 256
#define MAX_DIGESTS_PER_CHAINED_TABLE (MAX_DIGESTS_PER_CHAINED_FILE - 1)
#define MAX_DIGESTS_PER_BUF 16
#ifndef O_BINARY
#define O_BINARY 0
#define O_TEXT 0
#endif
struct vip_table_generator {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA2_CTX ctx;
FILE *digest_table_fd;
size_t digest_num_written;
const char *path;
};
void vip_transfer_deinit(struct qdl_device *qdl);
static void print_digest(unsigned char *buf)
{
char hex_str[SHA256_DIGEST_STRING_LENGTH];
for (size_t i = 0; i < SHA256_DIGEST_LENGTH; ++i)
sprintf(hex_str + i * 2, "%02x", buf[i]);
hex_str[SHA256_DIGEST_STRING_LENGTH - 1] = '\0';
ux_debug("FIREHOSE PACKET SHA256: %s\n", hex_str);
}
int vip_gen_init(struct qdl_device *qdl, const char *path)
{
struct vip_table_generator *vip_gen;
struct stat st;
char filepath[PATH_MAX];
if (qdl->dev_type != QDL_DEVICE_SIM) {
ux_err("Should be executed in simulation dry-run mode\n");
return -1;
}
if (stat(path, &st) || !S_ISDIR(st.st_mode)) {
ux_err("Directory '%s' to store VIP tables doesn't exist\n", path);
return -1;
}
vip_gen = malloc(sizeof(struct vip_table_generator));
if (!vip_gen) {
ux_err("Can't allocate memory for vip_table_generator\n");
return -1;
}
if (!sim_set_digest_generation(true, qdl, vip_gen)) {
ux_err("Can't enable digest table generation\n");
goto out_cleanup;
}
vip_gen->digest_num_written = 0;
vip_gen->path = path;
snprintf(filepath, sizeof(filepath), "%s/%s", path, DIGEST_FULL_TABLE_FILE);
vip_gen->digest_table_fd = fopen(filepath, "wb");
if (!vip_gen->digest_table_fd) {
ux_err("Can't create %s file\n", filepath);
goto out_cleanup;
}
return 0;
out_cleanup:
free(vip_gen);
sim_set_digest_generation(false, qdl, NULL);
return -1;
}
void vip_gen_chunk_init(struct qdl_device *qdl)
{
struct vip_table_generator *vip_gen;
vip_gen = sim_get_vip_generator(qdl);
if (!vip_gen)
return;
SHA256Init(&vip_gen->ctx);
}
void vip_gen_chunk_update(struct qdl_device *qdl, const void *buf, size_t len)
{
struct vip_table_generator *vip_gen;
vip_gen = sim_get_vip_generator(qdl);
if (!vip_gen)
return;
SHA256Update(&vip_gen->ctx, (uint8_t *)buf, len);
}
void vip_gen_chunk_store(struct qdl_device *qdl)
{
struct vip_table_generator *vip_gen;
vip_gen = sim_get_vip_generator(qdl);
if (!vip_gen)
return;
SHA256Final(vip_gen->hash, &vip_gen->ctx);
print_digest(vip_gen->hash);
if (fwrite(vip_gen->hash, SHA256_DIGEST_LENGTH, 1, vip_gen->digest_table_fd) != 1) {
ux_err("Failed to write digest to the " DIGEST_FULL_TABLE_FILE);
goto out_cleanup;
}
vip_gen->digest_num_written++;
return;
out_cleanup:
fclose(vip_gen->digest_table_fd);
}
static int write_output_file(const char *filename, bool append, const void *data, size_t len)
{
FILE *fp;
char *mode = "wb";
if (append)
mode = "ab";
fp = fopen(filename, mode);
if (!fp) {
ux_err("Failed to open file for appending\n");
return -1;
}
if (fwrite(data, 1, len, fp) != len) {
ux_err("Failed to append to file\n");
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
static int calculate_hash_of_file(const char *filename, unsigned char *hash)
{
unsigned char buf[1024];
SHA2_CTX ctx;
FILE *fp = fopen(filename, "rb");
if (!fp) {
ux_err("Failed to open file for hashing\n");
return -1;
}
SHA256Init(&ctx);
size_t bytes;
while ((bytes = fread(buf, 1, sizeof(buf), fp)) > 0) {
SHA256Update(&ctx, (uint8_t *)buf, bytes);
}
fclose(fp);
SHA256Final(hash, &ctx);
return 0;
}
static int write_digests_to_table(char *src_table, char *dest_table, size_t start_digest, size_t count)
{
const size_t elem_size = SHA256_DIGEST_LENGTH;
unsigned char buf[MAX_DIGESTS_PER_BUF * SHA256_DIGEST_LENGTH];
size_t written = 0;
int ret;
int fd = open(src_table, O_RDONLY | O_BINARY);
if (fd < 0) {
ux_err("Failed to open %s for reading\n", src_table);
return -1;
}
/* Seek to offset of start_digest */
off_t offset = elem_size * start_digest;
if (lseek(fd, offset, SEEK_SET) != offset) {
ux_err("Failed to seek in %s\n", src_table);
goto out_cleanup;
}
while (written < (count * elem_size)) {
size_t to_read = count * elem_size - written;
if (to_read > sizeof(buf))
to_read = sizeof(buf);
ssize_t bytes = read(fd, buf, to_read);
if (bytes < 0 || (size_t)bytes != to_read) {
ux_err("Failed to read from %s\n", src_table);
goto out_cleanup;
}
ret = write_output_file(dest_table, (written != 0), buf, bytes);
if (ret < 0) {
ux_err("Can't write digests to %s\n", dest_table);
goto out_cleanup;
}
written += to_read;
}
close(fd);
return 0;
out_cleanup:
close(fd);
return -1;
}
static int create_chained_tables(struct vip_table_generator *vip_gen)
{
size_t chained_num = 0;
size_t tosign_count = 0;
size_t total_digests = vip_gen->digest_num_written;
char src_table[PATH_MAX];
char dest_table[PATH_MAX];
unsigned char hash[SHA256_DIGEST_LENGTH];
int ret;
snprintf(src_table, sizeof(src_table), "%s/%s", vip_gen->path, DIGEST_FULL_TABLE_FILE);
/* Step 1: Write digest table to DigestsToSign.bin */
snprintf(dest_table, sizeof(dest_table), "%s/%s",
vip_gen->path, DIGEST_TABLE_TO_SIGN_FILE);
tosign_count = total_digests < MAX_DIGESTS_PER_SIGNED_TABLE ? total_digests :
MAX_DIGESTS_PER_SIGNED_TABLE;
ret = write_digests_to_table(src_table, dest_table, 0, tosign_count);
if (ret) {
ux_err("Writing digests to %s failed\n", dest_table);
return ret;
}
/* Step 2: Write remaining digests to ChainedTableOfDigests<n>.bin */
if (total_digests > MAX_DIGESTS_PER_SIGNED_TABLE) {
size_t remaining_digests = total_digests - MAX_DIGESTS_PER_SIGNED_TABLE;
while (remaining_digests > 0) {
size_t table_digests = remaining_digests > MAX_DIGESTS_PER_CHAINED_TABLE ?
MAX_DIGESTS_PER_CHAINED_TABLE : remaining_digests;
snprintf(dest_table, sizeof(dest_table),
"%s/%s%zu.bin", vip_gen->path,
CHAINED_TABLE_FILE_PREF, chained_num);
ret = write_digests_to_table(src_table, dest_table,
total_digests - remaining_digests,
table_digests);
if (ret) {
ux_err("Writing digests to %s failed\n", dest_table);
return ret;
}
remaining_digests -= table_digests;
if (!remaining_digests) {
/* Add zero (the packet can't be multiple of 512 bytes) */
ret = write_output_file(dest_table, true, "\0", 1);
if (ret < 0) {
ux_err("Can't write 0 to %s\n", dest_table);
return ret;
}
}
chained_num++;
}
}
/* Step 3: Recursively hash and append backwards */
for (ssize_t i = chained_num - 1; i >= 0; --i) {
snprintf(src_table, sizeof(src_table),
"%s/%s%zd.bin", vip_gen->path,
CHAINED_TABLE_FILE_PREF, i);
ret = calculate_hash_of_file(src_table, hash);
if (ret < 0) {
ux_err("Failed to hash %s\n", src_table);
return ret;
}
if (i == 0) {
snprintf(dest_table, sizeof(dest_table), "%s/%s",
vip_gen->path, DIGEST_TABLE_TO_SIGN_FILE);
ret = write_output_file(dest_table, true, hash, SHA256_DIGEST_LENGTH);
if (ret < 0) {
ux_err("Failed to append hash to %s\n", dest_table);
return ret;
}
} else {
snprintf(dest_table, sizeof(dest_table),
"%s/%s%zd.bin", vip_gen->path,
CHAINED_TABLE_FILE_PREF, (i - 1));
ret = write_output_file(dest_table, true, hash, SHA256_DIGEST_LENGTH);
if (ret < 0) {
ux_err("Failed to append hash to %s\n", dest_table);
return ret;
}
}
}
return 0;
}
void vip_gen_finalize(struct qdl_device *qdl)
{
struct vip_table_generator *vip_gen;
vip_gen = sim_get_vip_generator(qdl);
if (!vip_gen)
return;
fclose(vip_gen->digest_table_fd);
ux_debug("VIP TABLE DIGESTS: %lu\n", vip_gen->digest_num_written);
if (create_chained_tables(vip_gen) < 0)
ux_err("Error occurred when creating table of digests\n");
free(vip_gen);
sim_set_digest_generation(false, qdl, NULL);
}
int vip_transfer_init(struct qdl_device *qdl, const char *vip_table_path)
{
char fullpath[PATH_MAX];
snprintf(fullpath, sizeof(fullpath), "%s/%s",
vip_table_path, DIGEST_TABLE_TO_SIGN_FILE_MBN);
qdl->vip_data.signed_table_fd = open(fullpath, O_RDONLY);
if (!qdl->vip_data.signed_table_fd) {
ux_err("Can't open signed table %s\n", fullpath);
return -1;
}
qdl->vip_data.chained_num = 0;
for (int i = 0; i < MAX_CHAINED_FILES; ++i) {
snprintf(fullpath, sizeof(fullpath), "%s/%s%d%s",
vip_table_path, CHAINED_TABLE_FILE_PREF, i, ".bin");
int fd = open(fullpath, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT)
break;
ux_err("Can't open signed table %s\n", fullpath);
goto out_cleanup;
}
qdl->vip_data.chained_fds[qdl->vip_data.chained_num++] = fd;
}
qdl->vip_data.state = VIP_INIT;
qdl->vip_data.chained_cur = 0;
return 0;
out_cleanup:
vip_transfer_deinit(qdl);
return -1;
}
void vip_transfer_deinit(struct qdl_device *qdl)
{
close(qdl->vip_data.signed_table_fd);
if (qdl->vip_data.chained_num > 0) {
for (size_t i = 0; i < qdl->vip_data.chained_num - 1; ++i)
close(qdl->vip_data.chained_fds[i]);
}
}
static int vip_transfer_send_raw(struct qdl_device *qdl, int table_fd)
{
struct stat sb;
int ret;
void *buf;
ssize_t n;
ret = fstat(table_fd, &sb);
if (ret < 0) {
ux_err("Failed to stat digest table file\n");
return -1;
}
buf = malloc(sb.st_size);
if (!buf) {
ux_err("Failed to allocate transfer buffer\n");
return -1;
}
n = read(table_fd, buf, sb.st_size);
if (n < 0) {
ux_err("failed to read binary\n");
ret = -1;
goto out;
}
n = qdl_write(qdl, buf, sb.st_size, 1000);
if (n < 0) {
ux_err("USB write failed for data chunk\n");
ret = -1;
goto out;
}
out:
free(buf);
return ret;
}
int vip_transfer_handle_tables(struct qdl_device *qdl)
{
struct vip_transfer_data *vip_data = &qdl->vip_data;
int ret = 0;
if (vip_data->state == VIP_DISABLED)
return 0;
if (vip_data->state == VIP_INIT) {
/* Send initial signed table */
ret = vip_transfer_send_raw(qdl, vip_data->signed_table_fd);
if (ret) {
ux_err("VIP: failed to send the Signed VIP table\n");
return ret;
}
ux_debug("VIP: successfully sent the Initial VIP table\n");
vip_data->state = VIP_SEND_DATA;
vip_data->frames_sent = 0;
vip_data->frames_left = MAX_DIGESTS_PER_SIGNED_TABLE;
vip_data->fh_parse_status = true;
}
if (vip_data->state == VIP_SEND_NEXT_TABLE) {
if (vip_data->chained_cur >= vip_data->chained_num) {
ux_err("VIP: the required quantity of chained tables is missing\n");
return -1;
}
ret = vip_transfer_send_raw(qdl, vip_data->chained_fds[vip_data->chained_cur]);
if (ret) {
ux_err("VIP: failed to send the chained VIP table\n");
return ret;
}
ux_debug("VIP: successfully sent " CHAINED_TABLE_FILE_PREF "%lu.bin\n",
vip_data->chained_cur);
vip_data->state = VIP_SEND_DATA;
vip_data->frames_sent = 0;
vip_data->frames_left = MAX_DIGESTS_PER_CHAINED_TABLE;
vip_data->fh_parse_status = true;
vip_data->chained_cur++;
}
vip_data->frames_sent++;
if (vip_data->frames_sent >= vip_data->frames_left)
vip_data->state = VIP_SEND_NEXT_TABLE;
return 0;
}
bool vip_transfer_status_check_needed(struct qdl_device *qdl)
{
return qdl->vip_data.fh_parse_status;
}
void vip_transfer_clear_status(struct qdl_device *qdl)
{
qdl->vip_data.fh_parse_status = false;
}