I2C Out-of-Bounds Read in ASPEED I2C Controller
Version Information
Commit: 2339d0a1
Tag: v10.2.0-690-g2339d0a1cf
Issue Description and Vulnerability Analysis
This issue is an out-of-bounds read vulnerability in the ASPEED SoC I2C device emulation.
The affected device is the ASPEED I2C controller, which is used by several ASPEED SoC models, including AST2600, AST1030, and AST2700.
Source files:
hw/i2c/aspeed_i2c.c (implementation)
hw/i2c/aspeed_i2c.h (register array definition)
Root Cause Analysis
In the AspeedI2CBus structure, the register array regs is defined with only 28 dwords, corresponding to a total size of 0x70 bytes:
#define ASPEED_I2C_NEW_NUM_REG 28
...
struct AspeedI2CBus {
...
uint32_t regs[ASPEED_I2C_NEW_NUM_REG];
...
};
However, during initialization of the ASPEED I2C controller classes for AST2600, AST1030, and AST2700, the reg_size field is set to 0x80 bytes, exposing a larger MMIO register window than the actual backing storage:
static void aspeed_2600_i2c_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
AspeedI2CClass *aic = ASPEED_I2C_CLASS(klass);
dc->desc = "ASPEED 2600 I2C Controller";
aic->num_busses = 16;
aic->reg_size = 0x80; // <------ set 0x80
aic->gap = -1;
aic->bus_get_irq = aspeed_2600_i2c_bus_get_irq;
aic->pool_size = 0x20;
aic->pool_base = 0xC00;
aic->bus_pool_base = aspeed_2500_i2c_bus_pool_base;
aic->has_dma = true;
aic->mem_size = 0x1000;
}
Similar reg_size = 0x80 assignments exist in:
aspeed_1030_i2c_class_init
aspeed_2700_i2c_class_init
As a result, the MMIO region allows accesses to offsets in the range [0x70, 0x7f], even though the backing regs array only covers [0x00, 0x6f].
Vulnerable Read Path
In both the legacy and new I2C register read handlers, no bounds checking is performed before indexing the regs array:
static uint64_t aspeed_i2c_bus_old_read(AspeedI2CBus *bus, hwaddr offset,
unsigned size)
{
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
uint64_t value = bus->regs[offset / sizeof(*bus->regs)]; // <------ can read[0x70,0x7f)
...
}
static uint64_t aspeed_i2c_bus_new_read(AspeedI2CBus *bus, hwaddr offset,
unsigned size)
{
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
uint64_t value = bus->regs[offset / sizeof(*bus->regs)]; // <------ can read [0x70,0x7f)
...
}
Build Configuration
The issue was reproduced using QEMU built with AddressSanitizer and UndefinedBehaviorSanitizer enabled:
CC=clang CXX=clang++ \
../configure \
--target-list=arm-softmmu \
--enable-debug \
--enable-asan --enable-ubsan \
--disable-werror \
--extra-cflags="-fsanitize=address,undefined -fno-omit-frame-pointer" \
--extra-cxxflags="-fsanitize=address,undefined -fno-omit-frame-pointer" \
--extra-ldflags="-fsanitize=address,undefined"
make -j8
Reproduction Steps
The vulnerability can be triggered with the following qtest command:
ASAN_OPTIONS=abort_on_error=1:halt_on_error=1:detect_leaks=0 \
UBSAN_OPTIONS=abort_on_error=1:halt_on_error=1:print_stacktrace=1 \
cat << 'EOF' | /home/kiki/bug-verify/qemu-test/build/qemu-system-arm \
-machine ast2600-evb \
-display none -monitor none -serial none \
-qtest stdio -accel qtest
readl 0x1e78a2f0
EOF
Crash Information (UBSan)
==162942==WARNING: ASan doesn't fully support makecontext/swapcontext functions and may produce false positives in some cases!
[I 0.000000] OPENED
[R +0.176905] readl 0x1e78a2f0
../hw/i2c/aspeed_i2c.c:97:22: runtime error: index 28 out of bounds for type 'uint32_t[28]' (aka 'unsigned int[28]')
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../hw/i2c/aspeed_i2c.c:97:22 in
[S +0.177090] OK 0x00000000ffffffff
OK 0x00000000ffffffff
[I +0.177142] CLOSED
The read operation still returns a value to the guest, indicating that memory beyond the regs array is accessed.