MXS-2457 Allow string arguments to be treated as fields
Before this change, the masking could be bypassed simply by > set @@sql_mode='ANSI_QUOTES'; > select concat("ssn") from person; The reason is that as the query classifier is not aware of whether 'ANSI_QUOTES' is on or not, it will not know that what above appears to be the string "ssn", actually is the field name `ssn`. Consequently, the select will not be blocked and the result returned in cleartext. It's now possible to instruct the query classifier to report all string arguments of functions as fields, which will prevent the above. However, it will also mean that there may be false positives.
This commit is contained in:
parent
09d04a09d4
commit
f09d46c8e6
@ -30,6 +30,16 @@ typedef enum qc_init_kind
|
||||
QC_INIT_BOTH = 0x03
|
||||
} qc_init_kind_t;
|
||||
|
||||
/**
|
||||
* qc_option_t defines options that affect the classification.
|
||||
*/
|
||||
enum qc_option_t
|
||||
{
|
||||
QC_OPTION_STRING_ARG_AS_FIELD = (1 << 0), /*< Report a string argument to a function as a field. */
|
||||
};
|
||||
|
||||
const uint32_t QC_OPTION_MASK = QC_OPTION_STRING_ARG_AS_FIELD;
|
||||
|
||||
/**
|
||||
* qc_sql_mode_t specifies what should be assumed of the statements
|
||||
* that will be parsed.
|
||||
@ -441,6 +451,22 @@ typedef struct query_classifier
|
||||
* @param info The info to be closed.
|
||||
*/
|
||||
void (* qc_info_close)(QC_STMT_INFO* info);
|
||||
|
||||
/**
|
||||
* Gets the options of the *calling* thread.
|
||||
*
|
||||
* @return Bit mask of values from qc_option_t.
|
||||
*/
|
||||
uint32_t (* qc_get_options)();
|
||||
|
||||
/**
|
||||
* Sets the options for the *calling* thread.
|
||||
*
|
||||
* @param options Bits from qc_option_t.
|
||||
*
|
||||
* @return QC_RESULT_OK if @c options is valid, otherwise QC_RESULT_ERROR.
|
||||
*/
|
||||
int32_t (* qc_set_options)(uint32_t options);
|
||||
} QUERY_CLASSIFIER;
|
||||
|
||||
/**
|
||||
@ -952,4 +978,20 @@ json_t* qc_get_cache_stats_as_json();
|
||||
*/
|
||||
const char* qc_result_to_string(qc_parse_result_t result);
|
||||
|
||||
/**
|
||||
* Gets the options of the *calling* thread.
|
||||
*
|
||||
* @return Bit mask of values from qc_option_t.
|
||||
*/
|
||||
uint32_t qc_get_options();
|
||||
|
||||
/**
|
||||
* Sets the options for the *calling* thread.
|
||||
*
|
||||
* @param options Bits from qc_option_t.
|
||||
*
|
||||
* @return true if the options were valid, false otherwise.
|
||||
*/
|
||||
bool qc_set_options(uint32_t options);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
@ -195,6 +195,7 @@ static struct
|
||||
static thread_local struct
|
||||
{
|
||||
qc_sql_mode_t sql_mode;
|
||||
uint32_t options;
|
||||
NAME_MAPPING* function_name_mappings;
|
||||
// The version information is not used; the embedded library parses according
|
||||
// to the version of the embedded library it has been linked with. However, we
|
||||
@ -203,6 +204,7 @@ static thread_local struct
|
||||
} this_thread =
|
||||
{
|
||||
QC_SQL_MODE_DEFAULT,
|
||||
0,
|
||||
function_name_mappings_default,
|
||||
0
|
||||
};
|
||||
@ -2475,6 +2477,19 @@ static void add_function_field_usage(st_select_lex* select,
|
||||
add_function_field_usage(select, static_cast<Item_field*>(item), fi);
|
||||
break;
|
||||
|
||||
case Item::STRING_ITEM:
|
||||
if (this_thread.options & QC_OPTION_STRING_ARG_AS_FIELD)
|
||||
{
|
||||
String* s = item->val_str();
|
||||
int len = s->length();
|
||||
char tmp[len + 1];
|
||||
memcpy(tmp, s->ptr(), len);
|
||||
tmp[len] = 0;
|
||||
|
||||
add_function_field_usage(nullptr, nullptr, tmp, fi);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// mxb_assert(!true);
|
||||
;
|
||||
@ -3526,6 +3541,27 @@ int32_t qc_mysql_set_sql_mode(qc_sql_mode_t sql_mode)
|
||||
return rv;
|
||||
}
|
||||
|
||||
uint32_t qc_mysql_get_options()
|
||||
{
|
||||
return this_thread.options;
|
||||
}
|
||||
|
||||
int32_t qc_mysql_set_options(uint32_t options)
|
||||
{
|
||||
int32_t rv = QC_RESULT_OK;
|
||||
|
||||
if ((options & ~QC_OPTION_MASK) == 0)
|
||||
{
|
||||
this_thread.options = options;
|
||||
}
|
||||
else
|
||||
{
|
||||
rv = QC_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPORTS
|
||||
*/
|
||||
@ -3561,6 +3597,8 @@ extern "C"
|
||||
qc_mysql_set_sql_mode,
|
||||
nullptr, // qc_info_dup not supported.
|
||||
nullptr, // qc_info_close not supported.
|
||||
qc_mysql_get_options,
|
||||
qc_mysql_set_options
|
||||
};
|
||||
|
||||
static MXS_MODULE info =
|
||||
|
@ -156,6 +156,7 @@ static thread_local struct
|
||||
bool initialized; // Whether the thread specific data has been initialized.
|
||||
sqlite3* pDb; // Thread specific database handle.
|
||||
qc_sql_mode_t sql_mode; // What sql_mode is used.
|
||||
uint32_t options; // Options affecting classification.
|
||||
QcSqliteInfo* pInfo; // The information for the current statement being classified.
|
||||
uint64_t version; // Encoded version number
|
||||
uint32_t version_major;
|
||||
@ -1141,6 +1142,13 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pExpr->op == TK_STRING)
|
||||
{
|
||||
if (this_thread.options & QC_OPTION_STRING_ARG_AS_FIELD)
|
||||
{
|
||||
zColumn = pExpr->u.zToken;
|
||||
}
|
||||
}
|
||||
|
||||
if (zColumn)
|
||||
{
|
||||
@ -4541,6 +4549,8 @@ static int32_t qc_sqlite_get_sql_mode(qc_sql_mode_t* sql_mode);
|
||||
static int32_t qc_sqlite_set_sql_mode(qc_sql_mode_t sql_mode);
|
||||
static QC_STMT_INFO* qc_sqlite_info_dup(QC_STMT_INFO* info);
|
||||
static void qc_sqlite_info_close(QC_STMT_INFO* info);
|
||||
static uint32_t qc_sqlite_get_options();
|
||||
static int32_t qc_sqlite_set_options(uint32_t options);
|
||||
|
||||
static bool get_key_and_value(char* arg, const char** pkey, const char** pvalue)
|
||||
{
|
||||
@ -5223,6 +5233,27 @@ void qc_sqlite_info_close(QC_STMT_INFO* info)
|
||||
static_cast<QcSqliteInfo*>(info)->dec_ref();
|
||||
}
|
||||
|
||||
uint32_t qc_sqlite_get_options()
|
||||
{
|
||||
return this_thread.options;
|
||||
}
|
||||
|
||||
int32_t qc_sqlite_set_options(uint32_t options)
|
||||
{
|
||||
int32_t rv = QC_RESULT_OK;
|
||||
|
||||
if ((options & ~QC_OPTION_MASK) == 0)
|
||||
{
|
||||
this_thread.options = options;
|
||||
}
|
||||
else
|
||||
{
|
||||
rv = QC_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPORTS
|
||||
*/
|
||||
@ -5258,6 +5289,8 @@ extern "C"
|
||||
qc_sqlite_set_sql_mode,
|
||||
qc_sqlite_info_dup,
|
||||
qc_sqlite_info_close,
|
||||
qc_sqlite_get_options,
|
||||
qc_sqlite_set_options
|
||||
};
|
||||
|
||||
static MXS_MODULE info =
|
||||
|
@ -96,9 +96,11 @@ class QCInfoCache;
|
||||
static thread_local struct
|
||||
{
|
||||
QCInfoCache* pInfo_cache;
|
||||
uint32_t options;
|
||||
} this_thread =
|
||||
{
|
||||
nullptr
|
||||
nullptr,
|
||||
0
|
||||
};
|
||||
|
||||
|
||||
@ -147,7 +149,8 @@ public:
|
||||
{
|
||||
const Entry& entry = i->second;
|
||||
|
||||
if (entry.sql_mode == this_unit.qc_sql_mode)
|
||||
if ((entry.sql_mode == this_unit.qc_sql_mode) &&
|
||||
(entry.options == this_thread.options))
|
||||
{
|
||||
mxb_assert(this_unit.classifier);
|
||||
this_unit.classifier->qc_info_dup(entry.pInfo);
|
||||
@ -157,7 +160,7 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the sql_mode has changed, we discard the existing result.
|
||||
// If the sql_mode or options has changed, we discard the existing result.
|
||||
erase(i);
|
||||
|
||||
++m_stats.misses;
|
||||
@ -197,7 +200,7 @@ public:
|
||||
{
|
||||
this_unit.classifier->qc_info_dup(pInfo);
|
||||
|
||||
m_infos.emplace(canonical_stmt, Entry(pInfo, this_unit.qc_sql_mode));
|
||||
m_infos.emplace(canonical_stmt, Entry(pInfo, this_unit.qc_sql_mode, this_thread.options));
|
||||
|
||||
++m_stats.inserts;
|
||||
m_stats.size += size;
|
||||
@ -213,14 +216,16 @@ public:
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
Entry(QC_STMT_INFO* pInfo, qc_sql_mode_t sql_mode)
|
||||
Entry(QC_STMT_INFO* pInfo, qc_sql_mode_t sql_mode, uint32_t options)
|
||||
: pInfo(pInfo)
|
||||
, sql_mode(sql_mode)
|
||||
, options(options)
|
||||
{
|
||||
}
|
||||
|
||||
QC_STMT_INFO* pInfo;
|
||||
qc_sql_mode_t sql_mode;
|
||||
uint32_t options;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<std::string, Entry> InfosByStmt;
|
||||
@ -1282,6 +1287,29 @@ void qc_set_sql_mode(qc_sql_mode_t sql_mode)
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t qc_get_options()
|
||||
{
|
||||
QC_TRACE();
|
||||
mxb_assert(this_unit.classifier);
|
||||
|
||||
return this_unit.classifier->qc_get_options();
|
||||
}
|
||||
|
||||
bool qc_set_options(uint32_t options)
|
||||
{
|
||||
QC_TRACE();
|
||||
mxb_assert(this_unit.classifier);
|
||||
|
||||
int32_t rv = this_unit.classifier->qc_set_options(options);
|
||||
|
||||
if (rv == QC_RESULT_OK)
|
||||
{
|
||||
this_thread.options = options;
|
||||
}
|
||||
|
||||
return rv == QC_RESULT_OK;
|
||||
}
|
||||
|
||||
void qc_get_cache_properties(QC_CACHE_PROPERTIES* properties)
|
||||
{
|
||||
properties->max_size = this_unit.cache_max_size();
|
||||
|
Loading…
x
Reference in New Issue
Block a user