/* * Support for decoding of VT ioctl commands. * * Copyright (c) 2019-2021 Eugene Syromyatnikov <evgsyr@gmail.com> * All rights reserved. * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "defs.h" #include <linux/kd.h> #include <linux/keyboard.h> #include "print_fields.h" #include "print_utils.h" #include "xlat/kd_default_led_flags.h" #include "xlat/kd_kbd_modes.h" #include "xlat/kd_kbd_types.h" #include "xlat/kd_keymap_flags.h" #include "xlat/kd_key_tables.h" #include "xlat/kd_key_types.h" #include "xlat/kd_key_fn_keys.h" #include "xlat/kd_key_fn_key_vals.h" #include "xlat/kd_key_spec_keys.h" #include "xlat/kd_key_pad_keys.h" #include "xlat/kd_key_dead_keys.h" #include "xlat/kd_key_cur_keys.h" #include "xlat/kd_key_shift_keys.h" #include "xlat/kd_key_ascii_keys.h" #include "xlat/kd_key_lock_keys.h" #include "xlat/kd_key_slock_keys.h" #include "xlat/kd_key_brl_keys.h" #include "xlat/kd_led_flags.h" #include "xlat/kd_meta_vals.h" #include "xlat/kd_modes.h" #define XLAT_MACROS_ONLY # include "xlat/kd_ioctl_cmds.h" #undef XLAT_MACROS_ONLY #ifdef HAVE_STRUCT_KBDIACRUC typedef struct kbdiacruc struct_kbdiacruc; #else typedef struct { unsigned int diacr; unsigned int base; unsigned int result; } struct_kbdiacruc; #endif #ifdef HAVE_STRUCT_KBDIACRSUC typedef struct kbdiacrsuc struct_kbdiacrsuc; #else typedef struct { unsigned int kb_cnt; struct_kbdiacruc kbdiacruc[256]; } struct_kbdiacrsuc; #endif enum { KERNEL_PIT_TICK_RATE = 1193182, KERNEL_E_TABSZ = 256, KERNEL_MAX_DIACR = 256, }; static int kiocsound(struct tcb *const tcp, const kernel_ulong_t arg) { unsigned int freq = arg ? KERNEL_PIT_TICK_RATE / arg : 0; tprint_arg_next(); PRINT_VAL_U(arg); if (xlat_verbose(xlat_verbosity) != XLAT_STYLE_RAW) { if (freq) tprintf_comment("%u Hz", freq); else tprints_comment("off"); } return RVAL_IOCTL_DECODED; } static int kd_mk_tone(struct tcb *const tcp, const unsigned int arg) { unsigned int ticks = arg >> 16; unsigned int count = arg & 0xFFFF; unsigned int freq = ticks && count ? KERNEL_PIT_TICK_RATE / count : 0; tprint_arg_next(); if (ticks) tprintf("%u<<16|%u", ticks, count); else PRINT_VAL_U(count); if (xlat_verbose(xlat_verbosity) != XLAT_STYLE_RAW) { if (freq) tprintf_comment("%u Hz, %u ms", freq, ticks); else tprints_comment("off"); } return RVAL_IOCTL_DECODED; } static void print_leds(struct tcb *const tcp, const kernel_ulong_t arg, const bool get, const bool dflt) { unsigned char val; if (get) { if (umove_or_printaddr(tcp, arg, &val)) return; } else { val = arg; } if (get) tprint_indirect_begin(); printflags(dflt ? kd_default_led_flags : kd_led_flags, val, "LED_???"); if (get) tprint_indirect_end(); } static int kd_leds(struct tcb *const tcp, const unsigned int code, const kernel_ulong_t arg) { bool get = false; bool dflt = false; switch (code) { case KDGETLED: case KDGKBLED: get = true; } switch (code) { case KDGKBLED: case KDSKBLED: dflt = true; } if (entering(tcp)) { tprint_arg_next(); if (get) return 0; } print_leds(tcp, arg, get, dflt); return RVAL_IOCTL_DECODED; } static int kd_get_kb_type(struct tcb *const tcp, const kernel_ulong_t arg) { unsigned char val; if (entering(tcp)) { tprint_arg_next(); return 0; } if (umove_or_printaddr(tcp, arg, &val)) return RVAL_IOCTL_DECODED; tprint_indirect_begin(); printxval(kd_kbd_types, val, "KB_???"); tprint_indirect_end(); return RVAL_IOCTL_DECODED; } static int kd_io(struct tcb *const tcp, kernel_ulong_t arg) { enum { GPFIRST = 0x3b4, GPLAST = 0x3df }; tprint_arg_next(); PRINT_VAL_X(arg); if (arg >= GPFIRST && arg <= GPLAST && xlat_verbose(xlat_verbosity) != XLAT_STYLE_RAW) tprintf_comment("GPFIRST + %" PRI_klu, arg - GPFIRST); return RVAL_IOCTL_DECODED; } static int kd_set_mode(struct tcb *const tcp, const kernel_ulong_t arg) { tprint_arg_next(); printxval(kd_modes, arg, "KD_???"); return RVAL_IOCTL_DECODED; } static int kd_get_mode(struct tcb *const tcp, const kernel_ulong_t arg) { unsigned int val; if (entering(tcp)) { tprint_arg_next(); return 0; } if (umove_or_printaddr(tcp, arg, &val)) return RVAL_IOCTL_DECODED; tprint_indirect_begin(); printxval(kd_modes, val, "KD_???"); tprint_indirect_end(); return RVAL_IOCTL_DECODED; } static int kd_screen_map(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { if (entering(tcp)) { tprint_arg_next(); if (get) return 0; } if (entering(tcp) || !syserror(tcp)) printstr_ex(tcp, arg, KERNEL_E_TABSZ, QUOTE_FORCE_HEX); else printaddr(arg); return RVAL_IOCTL_DECODED; } static bool print_scrmap_array_member(struct tcb *tcp, void *elem_buf, size_t elem_size, void *data) { unsigned short val = *(unsigned short *) elem_buf; if ((xlat_verbose(xlat_verbosity) != XLAT_STYLE_ABBREV) || ((val & ~UNI_DIRECT_MASK) != UNI_DIRECT_BASE)) PRINT_VAL_X(val); if (xlat_verbose(xlat_verbosity) == XLAT_STYLE_RAW) return true; if ((val & ~UNI_DIRECT_MASK) == UNI_DIRECT_BASE) (xlat_verbose(xlat_verbosity) == XLAT_STYLE_VERBOSE ? tprintf_comment : tprintf)("UNI_DIRECT_BASE+%#hx", val & UNI_DIRECT_MASK); return true; } static int kd_uni_screen_map(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { unsigned short elem; if (entering(tcp)) { tprint_arg_next(); if (get) return 0; } print_array(tcp, arg, KERNEL_E_TABSZ, &elem, sizeof(elem), tfetch_mem, print_scrmap_array_member, 0); return RVAL_IOCTL_DECODED; } static int kd_set_kbd_mode(struct tcb *const tcp, const unsigned int arg) { tprint_arg_next(); printxval_d(kd_kbd_modes, arg, "K_???"); return RVAL_IOCTL_DECODED; } static int kd_get_kbd_mode(struct tcb *const tcp, const kernel_ulong_t arg) { unsigned int val; if (entering(tcp)) { tprint_arg_next(); return 0; } if (umove_or_printaddr(tcp, arg, &val)) return RVAL_IOCTL_DECODED; tprint_indirect_begin(); printxval_d(kd_kbd_modes, val, "K_???"); tprint_indirect_end(); return RVAL_IOCTL_DECODED; } static int kd_kbd_entry(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { static const struct xlat *xlat_tables[] = { /* KT_LATIN */ [KT_FN] = kd_key_fn_keys, [KT_SPEC] = kd_key_spec_keys, [KT_PAD] = kd_key_pad_keys, [KT_DEAD] = kd_key_dead_keys, /* KT_CONS */ [KT_CUR] = kd_key_cur_keys, [KT_SHIFT] = kd_key_shift_keys, /* KT_META */ [KT_ASCII] = kd_key_ascii_keys, [KT_LOCK] = kd_key_lock_keys, /* KT_LETTER */ [KT_SLOCK] = kd_key_slock_keys, /* KT_DEAD2 */ [KT_BRL] = kd_key_brl_keys, }; struct kbentry val; unsigned char ktyp; unsigned char kval; const char *str = NULL; if (entering(tcp)) { tprint_arg_next(); if (umoven(tcp, arg, offsetofend(struct kbentry, kb_index), &val)) { printaddr(arg); return RVAL_IOCTL_DECODED; } tprint_struct_begin(); const char *keymap_str = xlookup(kd_key_tables, val.kb_table); if (keymap_str) { tprints_field_name("kb_table"); print_xlat_ex(val.kb_table, keymap_str, XLAT_STYLE_DEFAULT); } else { PRINT_FIELD_FLAGS(val, kb_table, kd_keymap_flags, "K_???"); } tprint_struct_next(); PRINT_FIELD_U(val, kb_index); if (get) return 0; } else if (syserror(tcp)) { goto out; } tprint_struct_next(); if (umove(tcp, arg + offsetof(struct kbentry, kb_value), &val.kb_value)) { tprints_field_name("kb_value"); tprint_unavailable(); goto out; } PRINT_FIELD_X(val, kb_value); if (xlat_verbose(xlat_verbosity) == XLAT_STYLE_RAW) goto out; ktyp = KTYP(val.kb_value); kval = KVAL(val.kb_value); if (ktyp < ARRAY_SIZE(xlat_tables) && xlat_tables[ktyp]) str = xlookup(xlat_tables[ktyp], val.kb_value); if (str) { tprints_comment(str); } else { tprint_comment_begin(); tprints_arg_begin("K"); printxvals_ex(ktyp, NULL, XLAT_STYLE_ABBREV, kd_key_types, NULL); tprint_arg_next(); switch (ktyp) { case KT_LATIN: case KT_META: case KT_LETTER: case KT_DEAD2: print_char(kval, SCF_QUOTES | SCF_ESC_WS); break; default: PRINT_VAL_X(kval); } tprint_arg_end(); tprint_comment_end(); } out: tprint_struct_end(); return RVAL_IOCTL_DECODED; } static int kd_kbd_str_entry(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { struct kbsentry val; if (entering(tcp)) { tprint_arg_next(); if (umove_or_printaddr(tcp, arg, &(val.kb_func))) return RVAL_IOCTL_DECODED; tprint_struct_begin(); PRINT_FIELD_XVAL(val, kb_func, kd_key_fn_key_vals, "KVAL(K_???"")"); if (get) return 0; } else if (syserror(tcp)) { goto out; } tprint_struct_next(); tprints_field_name("kb_string"); int ret = umovestr(tcp, arg + offsetof(struct kbsentry, kb_string), sizeof(val.kb_string), (char *) val.kb_string); if (ret < 0) { tprint_unavailable(); goto out; } if (print_quoted_string((char *) val.kb_string, MIN(max_strlen, (unsigned int) ret ?: sizeof(val.kb_string)), QUOTE_OMIT_TRAILING_0)) tprint_more_data_follows(); out: tprint_struct_end(); return RVAL_IOCTL_DECODED; } static bool print_kbdiacr_array_member(struct tcb *tcp, void *elem_buf, size_t elem_size, void *data) { struct kbdiacr *val = elem_buf; tprint_struct_begin(); PRINT_FIELD_CHAR(*val, diacr, SCF_QUOTES | SCF_ESC_WS); tprint_struct_next(); PRINT_FIELD_CHAR(*val, base, SCF_QUOTES | SCF_ESC_WS); tprint_struct_next(); PRINT_FIELD_CHAR(*val, result, SCF_QUOTES | SCF_ESC_WS); tprint_struct_end(); return true; } static int kd_diacr(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { unsigned int kb_cnt; /* struct kbdiacrs.kb_cnt */ struct kbdiacr elem; if (entering(tcp)) { tprint_arg_next(); if (get) return 0; } if (umove_or_printaddr(tcp, arg, &kb_cnt)) return RVAL_IOCTL_DECODED; tprint_struct_begin(); tprints_field_name("kb_cnt"); PRINT_VAL_U(kb_cnt); tprint_struct_next(); tprints_field_name("kbdiacr"); print_array_ex(tcp, arg + offsetof(struct kbdiacrs, kbdiacr), MIN(kb_cnt, KERNEL_MAX_DIACR), &elem, sizeof(elem), tfetch_mem, print_kbdiacr_array_member, 0, kb_cnt > KERNEL_MAX_DIACR ? PAF_ARRAY_TRUNCATED : 0, NULL, NULL); tprint_struct_end(); return RVAL_IOCTL_DECODED; } static bool print_kbdiacruc_array_member(struct tcb *tcp, void *elem_buf, size_t elem_size, void *data) { struct_kbdiacruc *val = elem_buf; tprint_struct_begin(); PRINT_FIELD_X(*val, diacr); tprint_struct_next(); PRINT_FIELD_X(*val, base); tprint_struct_next(); PRINT_FIELD_X(*val, result); tprint_struct_end(); return true; } static int kd_diacr_uc(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { unsigned int kb_cnt; /* struct kbdiacrs.kb_cnt */ struct_kbdiacruc elem; if (entering(tcp)) { tprint_arg_next(); if (get) return 0; } if (umove_or_printaddr(tcp, arg, &kb_cnt)) return RVAL_IOCTL_DECODED; tprint_struct_begin(); tprints_field_name("kb_cnt"); PRINT_VAL_U(kb_cnt); tprint_struct_next(); tprints_field_name("kbdiacruc"); print_array_ex(tcp, arg + offsetof(struct_kbdiacrsuc, kbdiacruc), MIN(kb_cnt, KERNEL_MAX_DIACR), &elem, sizeof(elem), tfetch_mem, print_kbdiacruc_array_member, 0, kb_cnt > KERNEL_MAX_DIACR ? PAF_ARRAY_TRUNCATED : 0, NULL, NULL); tprint_struct_end(); return RVAL_IOCTL_DECODED; } static int kd_keycode(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { struct kbkeycode val; if (entering(tcp)) { tprint_arg_next(); if (umove_or_printaddr(tcp, arg, &val)) return RVAL_IOCTL_DECODED; tprint_struct_begin(); PRINT_FIELD_X(val, scancode); tprint_struct_next(); PRINT_FIELD_X(val, keycode); if (get) return 0; goto end; } /* exiting */ if (syserror(tcp) || umove(tcp, arg + offsetof(struct kbkeycode, keycode), &val.keycode)) { tprint_struct_end(); return RVAL_IOCTL_DECODED; } tprint_value_changed(); PRINT_VAL_X(val.keycode); end: tprint_struct_end(); return RVAL_IOCTL_DECODED; } static int kd_sigaccept(struct tcb *const tcp, const kernel_ulong_t arg) { tprint_arg_next(); if (arg < INT_MAX) printsignal(arg); else PRINT_VAL_U(arg); return RVAL_IOCTL_DECODED; } static void print_kbd_repeat(struct kbd_repeat *val) { tprint_struct_begin(); PRINT_FIELD_D(*val, delay); tprint_struct_next(); PRINT_FIELD_D(*val, period); tprint_struct_end(); } static int kd_kbdrep(struct tcb *const tcp, const kernel_ulong_t arg) { struct kbd_repeat val; if (entering(tcp)) { tprint_arg_next(); if (umove_or_printaddr(tcp, arg, &val)) return RVAL_IOCTL_DECODED; print_kbd_repeat(&val); return 0; } /* exiting */ if (syserror(tcp) || umove(tcp, arg, &val)) return RVAL_IOCTL_DECODED; tprint_value_changed(); print_kbd_repeat(&val); return RVAL_IOCTL_DECODED; } static int kd_font(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { if (entering(tcp)) { tprint_arg_next(); if (get) return 0; } /* * [GP]IO_FONT are equivalent to KDFONTOP with width == 8, * height == 32, and charcount == 256, so the total size * is (width + 7) / 8 * height * charcount == 8192. */ if (exiting(tcp) && syserror(tcp)) printaddr(arg); else printstr_ex(tcp, arg, 8192, QUOTE_FORCE_HEX); return RVAL_IOCTL_DECODED; } static int kd_kbmeta(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { unsigned int val; if (entering(tcp)) { tprint_arg_next(); if (get) return 0; } if (get) { if (umove_or_printaddr(tcp, arg, &val)) return RVAL_IOCTL_DECODED; } else { val = arg; } if (get) tprint_indirect_begin(); printxval(kd_meta_vals, val, "K_???"); if (get) tprint_indirect_end(); return RVAL_IOCTL_DECODED; } static int kd_unimapclr(struct tcb *const tcp, const kernel_ulong_t arg) { struct unimapinit umi; tprint_arg_next(); if (umove_or_printaddr(tcp, arg, &umi)) return RVAL_IOCTL_DECODED; tprint_struct_begin(); PRINT_FIELD_U(umi, advised_hashsize); tprint_struct_next(); PRINT_FIELD_U(umi, advised_hashstep); tprint_struct_next(); PRINT_FIELD_U(umi, advised_hashlevel); tprint_struct_end(); return RVAL_IOCTL_DECODED; } static int kd_cmap(struct tcb *const tcp, const kernel_ulong_t arg, const bool get) { if (entering(tcp)) { tprint_arg_next(); if (get) return 0; } else { if (syserror(tcp)) { printaddr(arg); return RVAL_IOCTL_DECODED; } } printstr_ex(tcp, arg, 3 * 16, QUOTE_FORCE_HEX); return RVAL_IOCTL_DECODED; } int kd_ioctl(struct tcb *const tcp, const unsigned int code, kernel_ulong_t arg) { arg = truncate_kulong_to_current_wordsize(arg); switch (code) { case KIOCSOUND: return kiocsound(tcp, arg); case KDMKTONE: return kd_mk_tone(tcp, arg); case KDGETLED: case KDSETLED: case KDGKBLED: case KDSKBLED: return kd_leds(tcp, code, arg); case KDGKBTYPE: return kd_get_kb_type(tcp, arg); case KDADDIO: case KDDELIO: return kd_io(tcp, arg); case KDSETMODE: return kd_set_mode(tcp, arg); case KDGETMODE: return kd_get_mode(tcp, arg); case GIO_SCRNMAP: case PIO_SCRNMAP: return kd_screen_map(tcp, arg, code == GIO_SCRNMAP); case GIO_UNISCRNMAP: case PIO_UNISCRNMAP: return kd_uni_screen_map(tcp, arg, code == GIO_UNISCRNMAP); case KDGKBMODE: return kd_get_kbd_mode(tcp, arg); case KDSKBMODE: return kd_set_kbd_mode(tcp, arg); case KDGKBENT: case KDSKBENT: return kd_kbd_entry(tcp, arg, code == KDGKBENT); case KDGKBSENT: case KDSKBSENT: return kd_kbd_str_entry(tcp, arg, code == KDGKBSENT); case KDGKBDIACR: case KDSKBDIACR: return kd_diacr(tcp, arg, code == KDGKBDIACR); case KDGKBDIACRUC: case KDSKBDIACRUC: return kd_diacr_uc(tcp, arg, code == KDGKBDIACRUC); case KDGETKEYCODE: case KDSETKEYCODE: return kd_keycode(tcp, arg, code == KDGETKEYCODE); case KDSIGACCEPT: return kd_sigaccept(tcp, arg); case KDKBDREP: return kd_kbdrep(tcp, arg); case GIO_FONT: case PIO_FONT: return kd_font(tcp, arg, code == GIO_FONT); case KDGKBMETA: case KDSKBMETA: return kd_kbmeta(tcp, arg, code == KDGKBMETA); case PIO_UNIMAPCLR: return kd_unimapclr(tcp, arg); case GIO_CMAP: case PIO_CMAP: return kd_cmap(tcp, arg, code == GIO_CMAP); /* no arguments */ case KDENABIO: case KDDISABIO: case KDMAPDISP: case KDUNMAPDISP: case PIO_FONTRESET: return RVAL_IOCTL_DECODED; } /* GIO_UNIMAP, PIO_UNIMAP, GIO_FONTX, PIO_FONTX, KDFONTOP */ return kd_mpers_ioctl(tcp, code, arg); }