From: Yu Hao <miller.yu...@gmail.com> The current PS/2 keyboard's keycode enqueue mechanism is not atomic for multi-byte keycodes. If the queue's free space is insufficient, individual bytes of a single keycode might be dropped. This can cause the guest OS to receive only a partial sequence, potentially interpreting it as a valid but incorrect key event.
For example, the keycode for the Up Arrow is 0xE0, 0x48(a two-byte sequence). If the first byte (0xE0) is dropped, the guest would receive only 0x48. This byte could be misinterpreted as a different key press (e.g., the 'H' key or a numeric '8'), leading to unintended input behavior in the VM. Signed-off-by: Yu Hao <miller.yu...@gmail.com> --- hw/input/ps2.c | 266 +++++++++++++++++++++++++++++-------------------- 1 file changed, 160 insertions(+), 106 deletions(-) diff --git a/hw/input/ps2.c b/hw/input/ps2.c index 7f7b1fce2e..b9b9e8dc09 100644 --- a/hw/input/ps2.c +++ b/hw/input/ps2.c @@ -287,26 +287,54 @@ static void ps2_cqueue_reset(PS2State *s) } /* keycode is the untranslated scancode in the current scancode set. */ -static void ps2_put_keycode(void *opaque, int keycode) +static void ps2_put_keycodes_atomic(void *opaque, int cnt, int keycode[]) { PS2KbdState *s = opaque; PS2State *ps = PS2_DEVICE(s); + int i = 0; + int idx = 0; + int atomic_key[PS2_QUEUE_SIZE] = {0}; + + if (cnt > PS2_QUEUE_SIZE) { + return; + } + + for (i = 0; i < cnt; i++) { + trace_ps2_put_keycode(opaque, keycode[i]); + } - trace_ps2_put_keycode(opaque, keycode); qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL); - if (s->translate) { - if (keycode == 0xf0) { - s->need_high_bit = true; - } else if (s->need_high_bit) { - ps2_queue(ps, translate_table[keycode] | 0x80); - s->need_high_bit = false; + for (i = 0; i < cnt; i++) { + if (s->translate) { + if (keycode[i] == 0xf0) { + s->need_high_bit = true; + } else if (s->need_high_bit) { + s->need_high_bit = false; + atomic_key[idx++] = translate_table[keycode[i]] | 0x80; + } else { + atomic_key[idx++] = translate_table[keycode[i]]; + } } else { - ps2_queue(ps, translate_table[keycode]); + atomic_key[idx++] = keycode[i]; } - } else { - ps2_queue(ps, keycode); } + + if (idx == 0) { + return; + } + + if (PS2_QUEUE_SIZE - ps->queue.count < idx) { + qemu_log_mask(LOG_UNIMP, + "ps2: drop key due to insufficient queue space (%d < %d)\n", + PS2_QUEUE_SIZE - ps->queue.count, idx); + return; + } + + for (i = 0; i < idx; i++) { + ps2_queue_noirq(ps, atomic_key[i]); + } + ps2_raise_irq(ps); } static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src, @@ -317,6 +345,8 @@ static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src, int qcode; uint16_t keycode = 0; int mod; + int atomic_key[PS2_QUEUE_SIZE] = {0}; + int idx = 0; /* do not process events while disabled to prevent stream corruption */ if (!s->scan_enabled) { @@ -340,66 +370,76 @@ static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src, if (qcode == Q_KEY_CODE_PAUSE) { if (s->modifiers & (MOD_CTRL_L | MOD_CTRL_R)) { if (key->down) { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x46); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xc6); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x46; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xc6; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } else { if (key->down) { - ps2_put_keycode(s, 0xe1); - ps2_put_keycode(s, 0x1d); - ps2_put_keycode(s, 0x45); - ps2_put_keycode(s, 0xe1); - ps2_put_keycode(s, 0x9d); - ps2_put_keycode(s, 0xc5); + atomic_key[idx++] = 0xe1; + atomic_key[idx++] = 0x1d; + atomic_key[idx++] = 0x45; + atomic_key[idx++] = 0xe1; + atomic_key[idx++] = 0x9d; + atomic_key[idx++] = 0xc5; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } } else if (qcode == Q_KEY_CODE_PRINT) { if (s->modifiers & MOD_ALT_L) { if (key->down) { - ps2_put_keycode(s, 0xb8); - ps2_put_keycode(s, 0x38); - ps2_put_keycode(s, 0x54); + atomic_key[idx++] = 0xb8; + atomic_key[idx++] = 0x38; + atomic_key[idx++] = 0x54; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { - ps2_put_keycode(s, 0xd4); - ps2_put_keycode(s, 0xb8); - ps2_put_keycode(s, 0x38); + atomic_key[idx++] = 0xd4; + atomic_key[idx++] = 0xb8; + atomic_key[idx++] = 0x38; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } else if (s->modifiers & MOD_ALT_R) { if (key->down) { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xb8); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x38); - ps2_put_keycode(s, 0x54); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xb8; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x38; + atomic_key[idx++] = 0x54; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { - ps2_put_keycode(s, 0xd4); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xb8); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x38); + atomic_key[idx++] = 0xd4; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xb8; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x38; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } else if (s->modifiers & (MOD_SHIFT_L | MOD_CTRL_L | MOD_SHIFT_R | MOD_CTRL_R)) { if (key->down) { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x37); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x37; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xb7); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xb7; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } else { if (key->down) { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x2a); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x37); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x2a; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x37; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xb7); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xaa); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xb7; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xaa; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } } else if ((qcode == Q_KEY_CODE_LANG1 || qcode == Q_KEY_CODE_LANG2) @@ -411,12 +451,14 @@ static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src, } if (keycode) { if (keycode & 0xff00) { - ps2_put_keycode(s, keycode >> 8); + atomic_key[idx++] = keycode >> 8; } + if (!key->down) { keycode |= 0x80; } - ps2_put_keycode(s, keycode & 0xff); + atomic_key[idx++] = keycode & 0xff; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { qemu_log_mask(LOG_UNIMP, "ps2: ignoring key with qcode %d\n", qcode); @@ -426,78 +468,88 @@ static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src, if (qcode == Q_KEY_CODE_PAUSE) { if (s->modifiers & (MOD_CTRL_L | MOD_CTRL_R)) { if (key->down) { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x7e); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x7e); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x7e; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x7e; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } else { if (key->down) { - ps2_put_keycode(s, 0xe1); - ps2_put_keycode(s, 0x14); - ps2_put_keycode(s, 0x77); - ps2_put_keycode(s, 0xe1); - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x14); - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x77); + atomic_key[idx++] = 0xe1; + atomic_key[idx++] = 0x14; + atomic_key[idx++] = 0x77; + atomic_key[idx++] = 0xe1; + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x14; + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x77; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } } else if (qcode == Q_KEY_CODE_PRINT) { if (s->modifiers & MOD_ALT_L) { if (key->down) { - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x11); - ps2_put_keycode(s, 0x11); - ps2_put_keycode(s, 0x84); + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x11; + atomic_key[idx++] = 0x11; + atomic_key[idx++] = 0x84; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x84); - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x11); - ps2_put_keycode(s, 0x11); + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x84; + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x11; + atomic_key[idx++] = 0x11; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } else if (s->modifiers & MOD_ALT_R) { if (key->down) { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x11); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x11); - ps2_put_keycode(s, 0x84); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x11; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x11; + atomic_key[idx++] = 0x84; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x84); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x11); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x11); + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x84; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x11; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x11; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } else if (s->modifiers & (MOD_SHIFT_L | MOD_CTRL_L | MOD_SHIFT_R | MOD_CTRL_R)) { if (key->down) { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x7c); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x7c; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x7c); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x7c; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } else { if (key->down) { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x12); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0x7c); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x12; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0x7c; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x7c); - ps2_put_keycode(s, 0xe0); - ps2_put_keycode(s, 0xf0); - ps2_put_keycode(s, 0x12); + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x7c; + atomic_key[idx++] = 0xe0; + atomic_key[idx++] = 0xf0; + atomic_key[idx++] = 0x12; + ps2_put_keycodes_atomic(s, idx, atomic_key); } } } else if ((qcode == Q_KEY_CODE_LANG1 || qcode == Q_KEY_CODE_LANG2) && @@ -509,12 +561,13 @@ static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src, } if (keycode) { if (keycode & 0xff00) { - ps2_put_keycode(s, keycode >> 8); + atomic_key[idx++] = keycode >> 8; } if (!key->down) { - ps2_put_keycode(s, 0xf0); + atomic_key[idx++] = 0xf0; } - ps2_put_keycode(s, keycode & 0xff); + atomic_key[idx++] = keycode & 0xff; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { qemu_log_mask(LOG_UNIMP, "ps2: ignoring key with qcode %d\n", qcode); @@ -527,9 +580,10 @@ static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src, if (keycode) { /* FIXME: break code should be configured on a key by key basis */ if (!key->down) { - ps2_put_keycode(s, 0xf0); + atomic_key[idx++] = 0xf0; } - ps2_put_keycode(s, keycode); + atomic_key[idx++] = keycode; + ps2_put_keycodes_atomic(s, idx, atomic_key); } else { qemu_log_mask(LOG_UNIMP, "ps2: ignoring key with qcode %d\n", qcode); -- 2.49.1