Model the A6 crypto, interrupt, USB, and platform blocks needed to boot SecureROM through iBSS into iBEC Recovery. Add local lab identity, IMG3, and APTicket tooling, patched macOS recovery utilities, UART and GDB access, and English end-user documentation.
224 lines
6.5 KiB
C
224 lines
6.5 KiB
C
/*
|
|
* Apple A6 (S5L8950X) SHA-1 accelerator used by the SecureROM.
|
|
*
|
|
* The ROM supplies already padded 64-byte SHA-1 blocks through the input
|
|
* window. QEMU's hash API expects the original message, so this model keeps
|
|
* the submitted blocks, validates/removes the standard SHA-1 padding, and
|
|
* exposes the resulting five digest words through the hardware registers.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "crypto/hash.h"
|
|
#include "hw/core/cpu.h"
|
|
#include "hw/misc/s5l8950x-sha1.h"
|
|
#include "hw/core/sysbus.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/units.h"
|
|
|
|
#define A6_SHA1_MMIO_SIZE 0x100
|
|
#define A6_SHA1_RESET 0x008
|
|
#define A6_SHA1_COMMAND 0x00c
|
|
#define A6_SHA1_STATUS 0x010
|
|
#define A6_SHA1_DIGEST_BASE 0x040
|
|
#define A6_SHA1_DIGEST_SIZE QCRYPTO_HASH_DIGEST_LEN_SHA1
|
|
#define A6_SHA1_INPUT_BASE 0x080
|
|
#define A6_SHA1_BLOCK_SIZE 64
|
|
#define A6_SHA1_MAX_MESSAGE (16 * MiB)
|
|
|
|
struct S5L8950XSHA1State {
|
|
SysBusDevice parent_obj;
|
|
|
|
MemoryRegion iomem;
|
|
uint32_t reset;
|
|
uint32_t command;
|
|
uint32_t status;
|
|
uint8_t input[A6_SHA1_BLOCK_SIZE];
|
|
uint8_t digest[A6_SHA1_DIGEST_SIZE];
|
|
GByteArray *padded;
|
|
bool digest_valid;
|
|
};
|
|
|
|
static bool s5l8950x_sha1_finalize(S5L8950XSHA1State *s)
|
|
{
|
|
uint64_t bit_length;
|
|
uint64_t message_length;
|
|
uint8_t *digest = s->digest;
|
|
size_t digest_length = sizeof(s->digest);
|
|
|
|
if (!s->padded->len || s->padded->len % A6_SHA1_BLOCK_SIZE ||
|
|
s->padded->len < 9) {
|
|
return false;
|
|
}
|
|
bit_length = ldq_be_p(s->padded->data + s->padded->len - 8);
|
|
if (bit_length & 7) {
|
|
return false;
|
|
}
|
|
message_length = bit_length / 8;
|
|
if (message_length > A6_SHA1_MAX_MESSAGE ||
|
|
message_length >= s->padded->len - 8 ||
|
|
s->padded->data[message_length] != 0x80) {
|
|
return false;
|
|
}
|
|
for (size_t i = message_length + 1; i < s->padded->len - 8; i++) {
|
|
if (s->padded->data[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
if (qcrypto_hash_bytes(QCRYPTO_HASH_ALGO_SHA1, s->padded->data,
|
|
message_length, &digest, &digest_length,
|
|
NULL) < 0 || digest_length != sizeof(s->digest)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"s5l8950x.sha1: SHA-1 calculation failed\n");
|
|
return false;
|
|
}
|
|
s->digest_valid = true;
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"s5l8950x.sha1: PC=0x%" VADDR_PRIx
|
|
" hashed %" PRIu64 " guest bytes\n",
|
|
current_cpu ? current_cpu->cc->get_pc(current_cpu) : 0,
|
|
message_length);
|
|
return true;
|
|
}
|
|
|
|
static void s5l8950x_sha1_submit(S5L8950XSHA1State *s)
|
|
{
|
|
if (s->padded->len > A6_SHA1_MAX_MESSAGE + 2 * A6_SHA1_BLOCK_SIZE) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"s5l8950x.sha1: guest message is too large\n");
|
|
return;
|
|
}
|
|
g_byte_array_append(s->padded, s->input, sizeof(s->input));
|
|
s->digest_valid = false;
|
|
}
|
|
|
|
static uint64_t s5l8950x_sha1_read(void *opaque, hwaddr offset,
|
|
unsigned size)
|
|
{
|
|
S5L8950XSHA1State *s = opaque;
|
|
|
|
if (offset >= A6_SHA1_INPUT_BASE &&
|
|
offset <= A6_SHA1_MMIO_SIZE - sizeof(uint32_t)) {
|
|
return ldl_le_p(s->input + offset - A6_SHA1_INPUT_BASE);
|
|
}
|
|
if (offset >= A6_SHA1_DIGEST_BASE &&
|
|
offset < A6_SHA1_DIGEST_BASE + A6_SHA1_DIGEST_SIZE) {
|
|
if (!s->digest_valid) {
|
|
s5l8950x_sha1_finalize(s);
|
|
}
|
|
return ldl_le_p(s->digest + offset - A6_SHA1_DIGEST_BASE);
|
|
}
|
|
switch (offset) {
|
|
case A6_SHA1_RESET:
|
|
return s->reset;
|
|
case A6_SHA1_COMMAND:
|
|
return s->command;
|
|
case A6_SHA1_STATUS:
|
|
return s->status;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void s5l8950x_sha1_write(void *opaque, hwaddr offset,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
S5L8950XSHA1State *s = opaque;
|
|
|
|
if (offset >= A6_SHA1_INPUT_BASE &&
|
|
offset <= A6_SHA1_MMIO_SIZE - sizeof(uint32_t)) {
|
|
stl_le_p(s->input + offset - A6_SHA1_INPUT_BASE, value);
|
|
return;
|
|
}
|
|
switch (offset) {
|
|
case A6_SHA1_RESET:
|
|
s->reset = value;
|
|
if (value & 0x10) {
|
|
g_byte_array_set_size(s->padded, 0);
|
|
memset(s->input, 0, sizeof(s->input));
|
|
memset(s->digest, 0, sizeof(s->digest));
|
|
s->digest_valid = false;
|
|
}
|
|
break;
|
|
case A6_SHA1_COMMAND:
|
|
s->command = value;
|
|
if (value & 1) {
|
|
s5l8950x_sha1_submit(s);
|
|
}
|
|
/* The SecureROM waits for bit zero to clear after each block. */
|
|
s->command &= ~1u;
|
|
break;
|
|
case A6_SHA1_STATUS:
|
|
s->status = value;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps s5l8950x_sha1_ops = {
|
|
.read = s5l8950x_sha1_read,
|
|
.write = s5l8950x_sha1_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid.min_access_size = 4,
|
|
.valid.max_access_size = 4,
|
|
};
|
|
|
|
static void s5l8950x_sha1_reset(DeviceState *dev)
|
|
{
|
|
S5L8950XSHA1State *s = S5L8950X_SHA1(dev);
|
|
|
|
s->reset = 0;
|
|
s->command = 0;
|
|
s->status = 0;
|
|
s->digest_valid = false;
|
|
memset(s->input, 0, sizeof(s->input));
|
|
memset(s->digest, 0, sizeof(s->digest));
|
|
if (s->padded) {
|
|
g_byte_array_set_size(s->padded, 0);
|
|
}
|
|
}
|
|
|
|
static void s5l8950x_sha1_init(Object *object)
|
|
{
|
|
S5L8950XSHA1State *s = S5L8950X_SHA1(object);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(object);
|
|
|
|
s->padded = g_byte_array_new();
|
|
memory_region_init_io(&s->iomem, object, &s5l8950x_sha1_ops, s,
|
|
TYPE_S5L8950X_SHA1, A6_SHA1_MMIO_SIZE);
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
}
|
|
|
|
static void s5l8950x_sha1_finalize_instance(Object *object)
|
|
{
|
|
S5L8950XSHA1State *s = S5L8950X_SHA1(object);
|
|
|
|
g_clear_pointer(&s->padded, g_byte_array_unref);
|
|
}
|
|
|
|
static void s5l8950x_sha1_class_init(ObjectClass *klass, const void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
device_class_set_legacy_reset(dc, s5l8950x_sha1_reset);
|
|
}
|
|
|
|
static const TypeInfo s5l8950x_sha1_type_info = {
|
|
.name = TYPE_S5L8950X_SHA1,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(S5L8950XSHA1State),
|
|
.instance_init = s5l8950x_sha1_init,
|
|
.instance_finalize = s5l8950x_sha1_finalize_instance,
|
|
.class_init = s5l8950x_sha1_class_init,
|
|
};
|
|
|
|
static void s5l8950x_sha1_register_types(void)
|
|
{
|
|
type_register_static(&s5l8950x_sha1_type_info);
|
|
}
|
|
|
|
type_init(s5l8950x_sha1_register_types)
|