From 3909a7a96b56d6b2694e0de79e9f7341c8b5e3c4 Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Fri, 30 Sep 2022 10:14:18 -0400 Subject: [PATCH] Fix: bytecode validator: reject specialized load field/context ref instructions Reject specialized load field/context ref instructions so a bytecode crafted with nefarious intent cannot: - Read user-space memory without proper get_user accessors, - Read a memory area larger than the memory targeted by the instrumentation. This prevents bytecode received from a tracing group user from oopsing the kernel or disclosing the content of kernel memory to the tracing group Signed-off-by: Mathieu Desnoyers Change-Id: I2bda938a3a050f20be1d3d542aefe638b1b8bf73 --- include/lttng/lttng-bytecode.h | 1 + src/lttng-bytecode-validator.c | 269 +++++++++++++++++++++++++++++++-- src/lttng-bytecode.c | 5 + 3 files changed, 262 insertions(+), 13 deletions(-) diff --git a/include/lttng/lttng-bytecode.h b/include/lttng/lttng-bytecode.h index 37c0e8a0..5bff5b00 100644 --- a/include/lttng/lttng-bytecode.h +++ b/include/lttng/lttng-bytecode.h @@ -292,6 +292,7 @@ const char *lttng_bytecode_print_op(enum bytecode_op op); void lttng_bytecode_sync_state(struct lttng_kernel_bytecode_runtime *runtime); int lttng_bytecode_validate(struct bytecode_runtime *bytecode); +int lttng_bytecode_validate_load(struct bytecode_runtime *bytecode); int lttng_bytecode_specialize(const struct lttng_kernel_event_desc *event_desc, struct bytecode_runtime *bytecode); diff --git a/src/lttng-bytecode-validator.c b/src/lttng-bytecode-validator.c index 2892ac7d..fe954a6e 100644 --- a/src/lttng-bytecode-validator.c +++ b/src/lttng-bytecode-validator.c @@ -400,21 +400,9 @@ int bytecode_validate_overflow(struct bytecode_runtime *bytecode, break; } - /* load field ref */ + /* load field and get context ref */ case BYTECODE_OP_LOAD_FIELD_REF: - { - printk(KERN_WARNING "LTTng: bytecode: Unknown field ref type\n"); - ret = -EINVAL; - break; - } - - /* get context ref */ case BYTECODE_OP_GET_CONTEXT_REF: - { - printk(KERN_WARNING "LTTng: bytecode: Unknown field ref type\n"); - ret = -EINVAL; - break; - } case BYTECODE_OP_LOAD_FIELD_REF_STRING: case BYTECODE_OP_LOAD_FIELD_REF_SEQUENCE: case BYTECODE_OP_LOAD_FIELD_REF_USER_STRING: @@ -1112,6 +1100,235 @@ int validate_instruction_all_contexts(struct bytecode_runtime *bytecode, return 0; } +/* + * Validate load instructions: specialized instructions not accepted as input. + * + * Return value: + * >0: going to next insn. + * 0: success, stop iteration. + * <0: error + */ +static +int validate_load(char **_next_pc, + char *pc) +{ + int ret = 0; + char *next_pc = *_next_pc; + + switch (*(bytecode_opcode_t *) pc) { + case BYTECODE_OP_UNKNOWN: + default: + { + printk(KERN_WARNING "LTTng: bytecode: unknown bytecode op %u\n", + (unsigned int) *(bytecode_opcode_t *) pc); + ret = -EINVAL; + goto end; + } + + case BYTECODE_OP_RETURN: + { + next_pc += sizeof(struct return_op); + break; + } + + case BYTECODE_OP_RETURN_S64: + { + next_pc += sizeof(struct return_op); + break; + } + + /* binary */ + case BYTECODE_OP_MUL: + case BYTECODE_OP_DIV: + case BYTECODE_OP_MOD: + case BYTECODE_OP_PLUS: + case BYTECODE_OP_MINUS: + /* Floating point */ + case BYTECODE_OP_EQ_DOUBLE: + case BYTECODE_OP_NE_DOUBLE: + case BYTECODE_OP_GT_DOUBLE: + case BYTECODE_OP_LT_DOUBLE: + case BYTECODE_OP_GE_DOUBLE: + case BYTECODE_OP_LE_DOUBLE: + case BYTECODE_OP_EQ_DOUBLE_S64: + case BYTECODE_OP_NE_DOUBLE_S64: + case BYTECODE_OP_GT_DOUBLE_S64: + case BYTECODE_OP_LT_DOUBLE_S64: + case BYTECODE_OP_GE_DOUBLE_S64: + case BYTECODE_OP_LE_DOUBLE_S64: + case BYTECODE_OP_EQ_S64_DOUBLE: + case BYTECODE_OP_NE_S64_DOUBLE: + case BYTECODE_OP_GT_S64_DOUBLE: + case BYTECODE_OP_LT_S64_DOUBLE: + case BYTECODE_OP_GE_S64_DOUBLE: + case BYTECODE_OP_LE_S64_DOUBLE: + case BYTECODE_OP_UNARY_PLUS_DOUBLE: + case BYTECODE_OP_UNARY_MINUS_DOUBLE: + case BYTECODE_OP_UNARY_NOT_DOUBLE: + case BYTECODE_OP_LOAD_FIELD_REF_DOUBLE: + case BYTECODE_OP_GET_CONTEXT_REF_DOUBLE: + case BYTECODE_OP_LOAD_DOUBLE: + case BYTECODE_OP_CAST_DOUBLE_TO_S64: + { + printk(KERN_WARNING "LTTng: bytecode: unsupported bytecode op %u\n", + (unsigned int) *(bytecode_opcode_t *) pc); + ret = -EINVAL; + goto end; + } + + case BYTECODE_OP_EQ: + case BYTECODE_OP_NE: + case BYTECODE_OP_GT: + case BYTECODE_OP_LT: + case BYTECODE_OP_GE: + case BYTECODE_OP_LE: + case BYTECODE_OP_EQ_STRING: + case BYTECODE_OP_NE_STRING: + case BYTECODE_OP_GT_STRING: + case BYTECODE_OP_LT_STRING: + case BYTECODE_OP_GE_STRING: + case BYTECODE_OP_LE_STRING: + case BYTECODE_OP_EQ_STAR_GLOB_STRING: + case BYTECODE_OP_NE_STAR_GLOB_STRING: + case BYTECODE_OP_EQ_S64: + case BYTECODE_OP_NE_S64: + case BYTECODE_OP_GT_S64: + case BYTECODE_OP_LT_S64: + case BYTECODE_OP_GE_S64: + case BYTECODE_OP_LE_S64: + case BYTECODE_OP_BIT_RSHIFT: + case BYTECODE_OP_BIT_LSHIFT: + case BYTECODE_OP_BIT_AND: + case BYTECODE_OP_BIT_OR: + case BYTECODE_OP_BIT_XOR: + { + next_pc += sizeof(struct binary_op); + break; + } + + /* unary */ + case BYTECODE_OP_UNARY_PLUS: + case BYTECODE_OP_UNARY_MINUS: + case BYTECODE_OP_UNARY_PLUS_S64: + case BYTECODE_OP_UNARY_MINUS_S64: + case BYTECODE_OP_UNARY_NOT_S64: + case BYTECODE_OP_UNARY_NOT: + case BYTECODE_OP_UNARY_BIT_NOT: + { + next_pc += sizeof(struct unary_op); + break; + } + + /* logical */ + case BYTECODE_OP_AND: + case BYTECODE_OP_OR: + { + next_pc += sizeof(struct logical_op); + break; + } + + /* load field ref */ + case BYTECODE_OP_LOAD_FIELD_REF: + /* get context ref */ + case BYTECODE_OP_GET_CONTEXT_REF: + { + next_pc += sizeof(struct load_op) + sizeof(struct field_ref); + break; + } + case BYTECODE_OP_LOAD_FIELD_REF_STRING: + case BYTECODE_OP_LOAD_FIELD_REF_SEQUENCE: + case BYTECODE_OP_GET_CONTEXT_REF_STRING: + case BYTECODE_OP_LOAD_FIELD_REF_USER_STRING: + case BYTECODE_OP_LOAD_FIELD_REF_USER_SEQUENCE: + case BYTECODE_OP_LOAD_FIELD_REF_S64: + case BYTECODE_OP_GET_CONTEXT_REF_S64: + { + /* + * Reject specialized load field ref instructions. + */ + ret = -EINVAL; + goto end; + } + + /* load from immediate operand */ + case BYTECODE_OP_LOAD_STRING: + case BYTECODE_OP_LOAD_STAR_GLOB_STRING: + { + struct load_op *insn = (struct load_op *) pc; + + next_pc += sizeof(struct load_op) + strlen(insn->data) + 1; + break; + } + + case BYTECODE_OP_LOAD_S64: + { + next_pc += sizeof(struct load_op) + sizeof(struct literal_numeric); + break; + } + + case BYTECODE_OP_CAST_TO_S64: + case BYTECODE_OP_CAST_NOP: + { + next_pc += sizeof(struct cast_op); + break; + } + + /* + * Instructions for recursive traversal through composed types. + */ + case BYTECODE_OP_GET_CONTEXT_ROOT: + case BYTECODE_OP_GET_APP_CONTEXT_ROOT: + case BYTECODE_OP_GET_PAYLOAD_ROOT: + case BYTECODE_OP_LOAD_FIELD: + { + next_pc += sizeof(struct load_op); + break; + } + + case BYTECODE_OP_LOAD_FIELD_S8: + case BYTECODE_OP_LOAD_FIELD_S16: + case BYTECODE_OP_LOAD_FIELD_S32: + case BYTECODE_OP_LOAD_FIELD_S64: + case BYTECODE_OP_LOAD_FIELD_U8: + case BYTECODE_OP_LOAD_FIELD_U16: + case BYTECODE_OP_LOAD_FIELD_U32: + case BYTECODE_OP_LOAD_FIELD_U64: + case BYTECODE_OP_LOAD_FIELD_STRING: + case BYTECODE_OP_LOAD_FIELD_SEQUENCE: + case BYTECODE_OP_LOAD_FIELD_DOUBLE: + { + /* + * Reject specialized load field instructions. + */ + ret = -EINVAL; + goto end; + } + + case BYTECODE_OP_GET_SYMBOL: + case BYTECODE_OP_GET_SYMBOL_FIELD: + { + next_pc += sizeof(struct load_op) + sizeof(struct get_symbol); + break; + } + + case BYTECODE_OP_GET_INDEX_U16: + { + next_pc += sizeof(struct load_op) + sizeof(struct get_index_u16); + break; + } + + case BYTECODE_OP_GET_INDEX_U64: + { + next_pc += sizeof(struct load_op) + sizeof(struct get_index_u64); + break; + } + + } +end: + *_next_pc = next_pc; + return ret; +} + /* * Return value: * >0: going to next insn. @@ -1734,6 +1951,32 @@ end: return ret; } +int lttng_bytecode_validate_load(struct bytecode_runtime *bytecode) +{ + char *pc, *next_pc, *start_pc; + int ret = -EINVAL; + + start_pc = &bytecode->code[0]; + for (pc = next_pc = start_pc; pc - start_pc < bytecode->len; + pc = next_pc) { + ret = bytecode_validate_overflow(bytecode, start_pc, pc); + if (ret != 0) { + if (ret == -ERANGE) + printk(KERN_WARNING "LTTng: bytecode: bytecode overflow\n"); + goto end; + } + dbg_printk("Validating loads: op %s (%u)\n", + lttng_bytecode_print_op((unsigned int) *(bytecode_opcode_t *) pc), + (unsigned int) *(bytecode_opcode_t *) pc); + + ret = validate_load(&next_pc, pc); + if (ret) + goto end; + } +end: + return ret; +} + /* * Never called concurrently (hash seed is shared). */ diff --git a/src/lttng-bytecode.c b/src/lttng-bytecode.c index 9a71a80c..a7b9129a 100644 --- a/src/lttng-bytecode.c +++ b/src/lttng-bytecode.c @@ -472,6 +472,11 @@ int link_bytecode(const struct lttng_kernel_event_desc *event_desc, runtime->len = bytecode->bc.reloc_offset; /* copy original bytecode */ memcpy(runtime->code, bytecode->bc.data, runtime->len); + /* Validate bytecode load instructions before relocs. */ + ret = lttng_bytecode_validate_load(runtime); + if (ret) { + goto link_error; + } /* * apply relocs. Those are a uint16_t (offset in bytecode) * followed by a string (field name). -- 2.34.1