uint_least32_t cursor_peek_u24le(struct cursor *cursor, size_t offset) { uint_least32_t a = cursor_peek_u8(cursor, offset); uint_least32_t b = cursor_peek_u8(cursor, offset + 1); uint_least32_t c = cursor_peek_u8(cursor, offset + 2); return a | (b << 8) | (c << 16); }
static bool is_query_valid(struct cursor *cursor, uint64_t potential_size, uint8_t offset) { SLOG(LOG_DEBUG, " Check query valid of potential size %"PRIu64" and offset %"PRIu8, potential_size, offset); // We check if last character is printable uint64_t last_char_pos = MIN(cursor->cap_len, potential_size) - 1; uint8_t last_char = cursor_peek_u8(cursor, last_char_pos); if (!is_print(last_char)) { SLOG(LOG_DEBUG, " Last char 0x%02x at pos %"PRIu64" is not printable", last_char, last_char_pos); return false; } // We check if last character + 1 is not printable. If it is printable, size might be incorrect // We assume chunked string if size >= 0x40 if (potential_size < MAX_OCI_CHUNK && potential_size < cursor->cap_len) { if (cursor->cap_len - 1 > potential_size) { char next_char = cursor_peek_u8(cursor, potential_size + 1); if (is_print(next_char)) { SLOG(LOG_DEBUG, " Char following last char 0x%02x is printable", next_char); return false; } } } // We check if first characters are printable if (PROTO_OK == is_range_print(cursor, MIN_QUERY_SIZE, offset)) { return true; } return false; }
/* * Check if query is valid. * @param cursor to read * @param candidate Filled with potential sql size candidate * @return True if a correct query has been found and cursor is positioned at the begin of the query */ static bool lookup_query(struct cursor *cursor, struct query_candidate *candidate) { SLOG(LOG_DEBUG, "Start looking for query"); uint8_t current; uint8_t next; while (cursor->cap_len > QUERY_WITH_SIZE) { current = cursor_peek_u8(cursor, 0); next = cursor_peek_u8(cursor, 1); if (current == TTC_ROW_DATA && next > 0 && next <= 4) { SLOG(LOG_DEBUG, " Looks like start of a data query row"); return false; } if (current > 0 && current < 3 && next > MIN_QUERY_SIZE && candidate->num_candidate_size < NB_ELEMS(candidate->candidate_sizes)) { uint64_t buf = 0; // Copy cursor since we might have pattern like 0x01 Size Query struct cursor cursor_copy = *cursor; if (PROTO_OK == cursor_read_variable_int(&cursor_copy, &buf)) { SLOG(LOG_DEBUG, " Found a candidate size %"PRIu64, buf); insert_array_sorted(candidate, buf); } } if (candidate->num_candidate_size == 0 || current >= candidate->candidate_sizes[0]) { if (check_chuncked_query(cursor, current, next, candidate)) return true; if (check_fixed_query(cursor, current, next, candidate)) return true; } else { if (check_fixed_query(cursor, current, next, candidate)) return true; if (check_chuncked_query(cursor, current, next, candidate)) return true; } cursor_drop(cursor, 1); } return false; }
static enum proto_parse_status tns_parse_sql_query_jdbc(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing a jdbc query"); enum proto_parse_status status; DROP_FIX(cursor, 1); uint_least64_t sql_len; status = cursor_read_variable_int(cursor, &sql_len); if (status != PROTO_OK) return status; SLOG(LOG_DEBUG, "Size sql %zu", sql_len); DROP_FIX(cursor, 1); // We have a number of fields at the end of the query uint_least64_t end_len; status = cursor_read_variable_int(cursor, &end_len); if (status != PROTO_OK) return status; DROP_FIX(cursor, 2); DROP_VARS(cursor, 3); DROP_FIX(cursor, 1); DROP_VAR(cursor); DROP_FIX(cursor, 6); DROP_VAR(cursor); char *sql = ""; if (sql_len > 0) { // Some unknown bytes while (cursor->cap_len > 1 && !isprint(cursor_peek_u8(cursor, 1))) { cursor_drop(cursor, 1); } CHECK(1); if (sql_len > 0xff && 0xff == cursor_peek_u8(cursor, 0)) { SLOG(LOG_DEBUG, "Looks like prefixed length chunks of size 0xff..."); status = cursor_read_chunked_string(cursor, &sql, 0xff); } else if (sql_len > 0x40 && 0x40 == cursor_peek_u8(cursor, 1)) { SLOG(LOG_DEBUG, "Looks like prefixed length chunks of size 0x40..."); cursor_drop(cursor, 1); status = cursor_read_chunked_string(cursor, &sql, 0x40); } else { if (!isprint(cursor_peek_u8(cursor, 0))) { cursor_drop(cursor, 1); } SLOG(LOG_DEBUG, "Looks like a fixed string of size %zu", sql_len); status = cursor_read_fixed_string(cursor, &sql, sql_len); } if (status != PROTO_OK) return status; } SLOG(LOG_DEBUG, "Sql parsed: %s", sql); sql_set_query(info, "%s", sql); SLOG(LOG_DEBUG, "Skipping %zu end variable fields", end_len); DROP_VARS(cursor, end_len); return PROTO_OK; }
/* * If oci, we will fallback on start query guesstimation * | 1 byte | * | Some flags (generally > 0x04) | * * If jdbc: * | 1 byte | 0-4 bytes | 1 byte | 0-4 bytes | * | Option size | Options | Var size | Var value | */ static bool is_oci(struct cursor *cursor) { CHECK(1); unsigned option_size = cursor_read_u8(cursor); CHECK(MAX(option_size, 2)); if (option_size > 0x04 || cursor_peek_u8(cursor, 1) == 0x00) return true; cursor_drop(cursor, option_size); // Should be a var here CHECK(1); unsigned var_size = cursor_read_u8(cursor); CHECK(MAX(var_size, 2)); if (var_size > 0x04 || cursor_peek_u8(cursor, 1) == 0x00) return true; cursor_drop(cursor, var_size); return false; }
static enum proto_parse_status is_range_print(struct cursor *cursor, size_t size) { CHECK(size); for (size_t i = 0; i < size; i++) { if (!isprint(cursor_peek_u8(cursor, i))) return PROTO_PARSE_ERR; } return PROTO_OK; }
static enum proto_parse_status is_range_print(struct cursor *cursor, size_t size, size_t offset) { CHECK(size + offset); SLOG(LOG_DEBUG, "Check range print with size %zu, and offset %zu", size, offset); for (size_t i = 0; i < size; i++) { uint8_t chr = cursor_peek_u8(cursor, i + offset); if (!is_print(chr)) { SLOG(LOG_DEBUG, "Character 0x%"PRIx8" at %zu is not printable", chr, i); return PROTO_PARSE_ERR; } } return PROTO_OK; }
static void cursor_check(void) { struct cursor cursor; static uint8_t const data[] = { 1, 2 }; cursor_ctor(&cursor, data, sizeof(data)); assert(cursor_peek_u8(&cursor, 0) == 0x01U); assert(cursor_peek_u8(&cursor, 1) == 0x02U); assert(cursor_read_u8(&cursor) == 0x01U); assert(cursor_read_u8(&cursor) == 0x02U); static uint16_t const data16[] = { 0x0102, 0x0304 }; cursor_ctor(&cursor, (uint8_t *)data16, sizeof(data16)); assert(cursor_peek_u16le(&cursor, 0) == 0x0102U); assert(cursor_peek_u16le(&cursor, 2) == 0x0304U); assert(cursor_read_u16(&cursor) == 0x0102U); assert(cursor_read_u16(&cursor) == 0x0304U); static uint32_t const data32[] = { 0x01020304U, 0x05060708U }; cursor_ctor(&cursor, (uint8_t *)data32, sizeof(data32)); assert(cursor_peek_u32le(&cursor, 0) == 0x01020304U); assert(cursor_peek_u32le(&cursor, 4) == 0x05060708U); assert(cursor_read_u32(&cursor) == 0x01020304U); assert(cursor_read_u32(&cursor) == 0x05060708U); static uint64_t const data64[] = { 0x0102030405060708ULL }; cursor_ctor(&cursor, (uint8_t *)data64, sizeof(data64)); assert(cursor_peek_u64le(&cursor, 0) == 0x0102030405060708ULL); assert(cursor_read_u64(&cursor) == 0x0102030405060708ULL); static uint8_t const datan[] = { 1, 2, 3, 4 }; cursor_ctor(&cursor, datan, sizeof(datan)); assert(cursor_peek_u32n(&cursor, 0) == 0x01020304); assert(cursor_read_u32n(&cursor) == 0x01020304); cursor_ctor(&cursor, datan, sizeof(datan)); assert(cursor_peek_u16n(&cursor, 0) == 0x0102); assert(cursor_read_u16n(&cursor) == 0x0102); }
static bool lookup_query(struct cursor *cursor) { #define MIN_QUERY_SIZE 10 #define QUERY_WITH_SIZE 12 while (cursor->cap_len > QUERY_WITH_SIZE) { uint8_t c; uint8_t potential_size = 0; do { c = cursor_peek_u8(cursor, 1); potential_size = cursor_read_u8(cursor); } while (cursor->cap_len > QUERY_WITH_SIZE && !isprint(c)); SLOG(LOG_DEBUG, "Found potential size 0x%02x and first printable %c", potential_size, c); // Check on found size if (potential_size < MIN_QUERY_SIZE || potential_size > cursor->cap_len) continue; // We check if last character is printable if (!isprint(cursor_peek_u8(cursor, potential_size - 1))) continue; // We check if first characters are printable if (PROTO_OK == is_range_print(cursor, MIN_QUERY_SIZE)) { cursor_rollback(cursor, 1); return true; } } return false; }
// | 1 byte | 1 byte | 2 bytes | 4 bytes | 4 bytes | 4 bytes | // | SMID (0x53) | Flag | SID | Length | Seq num | Window | static enum proto_parse_status tds_parse_smp_header(struct cursor *cursor, struct smp_header *out_header) { # define SMP_PKT_HDR_LEN 0x10 # define SMP_SMID 0x53 if (cursor_peek_u8(cursor, 0) == SMP_SMID) { CHECK_LEN(cursor, SMP_PKT_HDR_LEN, 0); cursor_drop(cursor, 1); out_header->flags = cursor_read_u8(cursor); out_header->sid = cursor_read_u16le(cursor); out_header->length = cursor_read_u32le(cursor); out_header->seq_num = cursor_read_u32le(cursor); out_header->window = cursor_read_u32le(cursor); } return PROTO_OK; }
/* * | 1 byte | 1 + 0-8 bytes | up to 5 vars | * | Flag | Number column | unknown | */ static enum proto_parse_status tns_parse_row_prefix(struct tns_parser *tns_parser, struct sql_proto_info *info, struct cursor *cursor) { enum proto_parse_status status; SLOG(LOG_DEBUG, "Parsing Row prefix"); DROP_FIX(cursor, 1); if (PROTO_OK != (status = read_field_count(tns_parser, info, cursor))) return status; for (unsigned i = 0; i < 5; i++) { CHECK(1); char c = cursor_peek_u8(cursor, 0); if (c == TTC_ROW_DATA || c == TTC_END_MESSAGE) return PROTO_OK; DROP_VAR(cursor); } return PROTO_OK; }
static enum proto_parse_status tns_parse_row_data(struct tns_parser *tns_parser, struct sql_proto_info *info, struct cursor *cursor) { enum proto_parse_status status; SLOG(LOG_DEBUG, "Parsing row data with %u fields", tns_parser->nb_fields); /* A row data contains : * - 1 var for each fields */ DROP_VAR_STRS(cursor, tns_parser->nb_fields); // Our nb fields might be incorrect CHECK(1); char c = cursor_peek_u8(cursor, 0); if (TTC_END_MESSAGE != c && TTC_ROW_RECAP != c) { DROP_VAR_STR(cursor); tns_parser->nb_fields++; } sql_set_field_count(info, tns_parser->nb_fields); return PROTO_OK; }
uint_least16_t cursor_peek_u16le(struct cursor *cursor, size_t offset) { uint_least32_t a = cursor_peek_u8(cursor, offset); uint_least32_t b = cursor_peek_u8(cursor, offset + 1); return a | (b << 8); }
uint_least16_t cursor_peek_u16n(struct cursor *cursor, size_t offset) { uint_least32_t a = cursor_peek_u8(cursor, offset); uint_least32_t b = cursor_peek_u8(cursor, offset + 1); return (a << 8) | b; }
static enum proto_parse_status tns_parse_sql_query_oci(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing an oci query"); uint8_t query_size = parse_query_header(cursor); struct query_candidate candidate = {.num_candidate_size = 0}; if (query_size > 0) { candidate.candidate_sizes[candidate.num_candidate_size++] = query_size; } bool has_query = lookup_query(cursor, &candidate); info->u.query.sql[0] = '\0'; info->u.query.truncated = 0; if (has_query) { SLOG(LOG_DEBUG, "Found a query, parsing it"); if (candidate.is_chunked) { cursor_read_chunked_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), MAX_OCI_CHUNK); } else { cursor_read_fixed_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), candidate.query_size); } } SLOG(LOG_DEBUG, "Sql parsed: %s", info->u.query.sql); info->set_values |= SQL_SQL; // Drop the rest if(cursor->cap_len > 0) cursor_drop(cursor, cursor->cap_len - 1); return PROTO_OK; } /* * | 1 byte | 1 + 0-8 bytes | 1 byte | 1 + 0-4 bytes | Lots of unknown bytes | variable | * | Unk | Sql len | Unk | Num end fields | Unk | sql query | */ static enum proto_parse_status tns_parse_sql_query_jdbc(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing a jdbc query"); enum proto_parse_status status = PROTO_OK; DROP_FIX(cursor, 1); uint_least64_t sql_len; if (PROTO_OK != (status = cursor_read_variable_int(cursor, &sql_len))) return status; SLOG(LOG_DEBUG, "Size sql %zu", sql_len); DROP_FIX(cursor, 1); DROP_VAR(cursor); DROP_FIX(cursor, 2); info->u.query.sql[0] = '\0'; info->u.query.truncated = 0; // TODO Handle truncated if (sql_len > 0) { // Some unknown bytes while (cursor->cap_len > 1 && PROTO_OK != is_range_print(cursor, MIN(MIN_QUERY_SIZE, sql_len), 1)) { // TODO drop to the first non printable cursor_drop(cursor, 1); } CHECK(1); if (sql_len > 0xff && 0xff == cursor_peek_u8(cursor, 0)) { SLOG(LOG_DEBUG, "Looks like prefixed length chunks of size 0xff..."); status = cursor_read_chunked_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), 0xff); } else if (sql_len > MAX_OCI_CHUNK && MAX_OCI_CHUNK == cursor_peek_u8(cursor, 0)) { SLOG(LOG_DEBUG, "Looks like prefixed length chunks of size 0x40..."); status = cursor_read_chunked_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), MAX_OCI_CHUNK); } else { // We don't care about the first non printable character cursor_drop(cursor, 1); CHECK(1); // In rare occurrence where sql_len == first character, we check the byte after the expected query, // if it's printable, the first character is probably the prefixed size. if (cursor_peek_u8(cursor, 0) == sql_len && sql_len < cursor->cap_len && is_print(cursor_peek_u8(cursor, sql_len))) cursor_drop(cursor, 1); SLOG(LOG_DEBUG, "Looks like a fixed string of size %zu", sql_len); int written_bytes = cursor_read_fixed_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), sql_len); if (written_bytes < 0) return PROTO_TOO_SHORT; } if (status != PROTO_OK) return status; } SLOG(LOG_DEBUG, "Sql parsed: %s", info->u.query.sql); info->set_values |= SQL_SQL; return PROTO_OK; }
static enum proto_parse_status tns_parse_end(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing tns end packet"); enum proto_parse_status status; uint_least64_t var[6]; for (unsigned i = 0; i < 6; i++) { if (PROTO_OK != (status = cursor_read_variable_int(cursor, var + i))) return status; } uint_least64_t nb_rows; uint_least64_t error_code; // let's use the double 0x00 to guess the position of row number and error code if (var[0] > 0 && var[4] == 0 && var[5] == 0) { // var[0] is unknown? // var[1] == sequence // var[2] == rows // var[3] == error code SLOG(LOG_DEBUG, "Unknown bits after ttc code"); nb_rows = var[2]; error_code = var[3]; DROP_VAR(cursor); } else if (var[3] == 0 && var[4] == 0) { // var[0] == sequence // var[1] == rows // var[2] == error code nb_rows = var[1]; error_code = var[2]; } else { // var[0] == rows // var[1] == error code nb_rows = var[0]; error_code = var[1]; } if (info->msg_type == SQL_QUERY) { sql_set_row_count(info, nb_rows); SLOG(LOG_DEBUG, "Nb rows %d", info->u.query.nb_rows); } SLOG(LOG_DEBUG, "Error code is %zu", error_code); DROP_VARS(cursor, 1); DROP_FIX(cursor, 2); DROP_VARS(cursor, 2); DROP_FIX(cursor, 2); DROP_VARS(cursor, 2); DROP_FIX(cursor, 1); DROP_VARS(cursor, 3); if (error_code != 0) { SLOG(LOG_DEBUG, "Parsing error message"); char *error_msg = tempstr(); unsigned error_len; // Drop an unknown number of bytes here while(cursor->cap_len > 2 && (cursor_peek_u8(cursor, 0) == 0 || !is_print(cursor_peek_u8(cursor, 1)))){ DROP_FIX(cursor, 1); } SLOG(LOG_DEBUG, "First printable char is %c", cursor_peek_u8(cursor, 1)); status = cursor_read_variable_string(cursor, error_msg, TEMPSTR_SIZE, &error_len); if (status != PROTO_OK) return status; if (error_len < 12) return PROTO_PARSE_ERR; // Split "ORA-XXXX: msg" // Part before : is the error code // Part after is the localized message char *colon_pos = memchr(error_msg, ':', error_len); sql_set_request_status(info, SQL_REQUEST_ERROR); if (colon_pos) { // We extract the error code unsigned error_code_size = colon_pos - error_msg; int size_err = MIN(error_code_size, sizeof(info->error_code)); memcpy(info->error_code, error_msg, size_err); info->error_code[size_err] = '\0'; info->set_values |= SQL_ERROR_CODE; if (0 == strcmp("ORA-01403", info->error_code)) info->request_status = SQL_REQUEST_COMPLETE; // We skip ':' in errror message char const *start_message = colon_pos + 1; // We skip spaces before errror message while (start_message < error_len + error_msg && *start_message == ' ') start_message++; copy_string(info->error_message, start_message, sizeof(info->error_message)); info->set_values |= SQL_ERROR_MESSAGE; } else { copy_string(info->error_message, error_msg, sizeof(info->error_message)); info->set_values |= SQL_ERROR_MESSAGE; } } return PROTO_OK; }