Add MariaDB 10.1.13 offsets. Format the code.

pull/141/head
Arnold Robbins 2016-03-31 11:55:57 +03:00
parent 051b1fd67e
commit 80646620e9
9 changed files with 3683 additions and 3626 deletions

View File

@ -49,12 +49,12 @@ typedef struct _THDPRINTED {
} THDPRINTED;
#define MAX_COMMAND_CHAR_NUMBERS 40
const char * retrieve_command (THD * thd, bool & is_sql_cmd);
const char *retrieve_command(THD *thd, bool & is_sql_cmd);
typedef size_t OFFSET;
#define MAX_COM_STATUS_VARS_RECORDS 512
//mysql max identifier is 64 so 2*64 + . and null
// mysql max identifier is 64 so 2*64 + . and null
#define MAX_OBJECT_CHAR_NUMBERS 131
#define MAX_USER_CHAR_NUMBERS 20
#define MAX_NUM_OBJECT_ELEM 256
@ -63,22 +63,21 @@ typedef size_t OFFSET;
/**
* The struct used to hold offsets. We should have one per version.
*/
typedef struct ThdOffsets
{
const char * version;
const char * md5digest;
OFFSET query_id;
OFFSET thread_id;
OFFSET main_security_ctx;
OFFSET command;
typedef struct ThdOffsets {
const char *version;
const char *md5digest;
OFFSET query_id;
OFFSET thread_id;
OFFSET main_security_ctx;
OFFSET command;
OFFSET lex;
OFFSET lex_comment;
OFFSET sec_ctx_user;
OFFSET sec_ctx_host;
OFFSET sec_ctx_ip;
OFFSET sec_ctx_priv_user;
OFFSET db;
OFFSET killed;
OFFSET db;
OFFSET killed;
} ThdOffsets;
/*
@ -96,267 +95,263 @@ typedef ssize_t (*audit_write_func)(const char *, size_t);
/**
* Interface for an io writer
*/
class IWriter
{
class IWriter {
public:
virtual ~IWriter() {}
//return negative on fail
virtual ssize_t write(const char * data, size_t size) = 0;
inline ssize_t write_str(const char * str)
{
return write(str, strlen(str));
}
//return 0 on success
virtual int open(const char * io_dest, bool log_errors) = 0;
virtual ~IWriter() {}
// return negative on fail
virtual ssize_t write(const char *data, size_t size) = 0;
inline ssize_t write_str(const char *str)
{
return write(str, strlen(str));
}
// return 0 on success
virtual int open(const char *io_dest, bool log_errors) = 0;
virtual void close() = 0;
};
class ThdSesData {
public:
// enum indicating from where the object list came from
enum ObjectIterType {OBJ_NONE, OBJ_DB, OBJ_QUERY_CACHE, OBJ_TABLE_LIST};
ThdSesData(THD *pTHD);
THD *getTHD() { return m_pThd;}
const char *getCmdName() { return m_CmdName; }
const char *getUserName() { return m_UserName; }
/**
* Start fetching objects. Return true if there are objects available.
*/
bool startGetObjects();
/**
* Get next object. Return true if populated. False if there isn't an object available.
* Will point the passed pointers to point to db, name and type.
* obj_type is optional and may be null.
*/
bool getNextObject(const char **db_name, const char **obj_name, const char **obj_type);
//enum indicating from where the object list came from
enum ObjectIterType {OBJ_NONE, OBJ_DB, OBJ_QUERY_CACHE, OBJ_TABLE_LIST};
ThdSesData(THD *pTHD);
THD* getTHD () { return m_pThd;}
const char * getCmdName () { return m_CmdName; }
const char * getUserName () { return m_UserName; }
/**
* Start fetching objects. Return true if there are objects available.
*/
bool startGetObjects();
/**
* Get next object. Return true if populated. False if there isn't an object available.
* Will point the passed pointers to point to db, name and type.
* obj_type is optional and may be null.
*/
bool getNextObject(const char ** db_name, const char ** obj_name, const char ** obj_type);
private:
THD *m_pThd;
const char *m_CmdName;
const char *m_UserName;
bool m_isSqlCmd;
enum ObjectIterType m_objIterType;
//pointer for iterating tables
TABLE_LIST * m_tables;
//indicator if we are at the first table
bool m_firstTable;
//used for query cache iter
QueryTableInf * m_tableInf;
int m_index;
THD *m_pThd;
const char *m_CmdName;
const char *m_UserName;
bool m_isSqlCmd;
enum ObjectIterType m_objIterType;
// pointer for iterating tables
TABLE_LIST *m_tables;
// indicator if we are at the first table
bool m_firstTable;
// used for query cache iter
QueryTableInf *m_tableInf;
int m_index;
protected:
ThdSesData (const ThdSesData& );
ThdSesData &operator =(const ThdSesData& );
ThdSesData(const ThdSesData&);
ThdSesData &operator =(const ThdSesData&);
};
/**
* Base for audit formatter
*/
class Audit_formatter
{
class Audit_formatter {
public:
virtual ~Audit_formatter() {}
virtual ~Audit_formatter() {}
/**
* static offsets to use for fetching THD data. Set by the audit plugin during startup.
*/
static ThdOffsets thd_offsets;
/**
* static offsets to use for fetching THD data. Set by the audit plugin during startup.
*/
static ThdOffsets thd_offsets;
/**
* Format an audit event from the passed THD. Will write out its output using the audit_write_func.
*
* @return -1 on a failure
*/
virtual ssize_t event_format(ThdSesData *pThdData, IWriter *writer) =0;
/**
* format a message when handler is started
* @return -1 on a failure
*/
virtual ssize_t start_msg_format(IWriter *writer) { return 0; }
/**
* format a message when handler is stopped
* @return -1 on a failure
*/
virtual ssize_t stop_msg_format(IWriter *writer) { return 0; }
/**
* Format an audit event from the passed THD. Will write out its output using the audit_write_func.
*
* @return -1 on a failure
*/
virtual ssize_t event_format(ThdSesData *pThdData, IWriter * writer) =0;
/**
* format a message when handler is started
* @return -1 on a failure
*/
virtual ssize_t start_msg_format(IWriter * writer) { return 0; }
/**
* format a message when handler is stopped
* @return -1 on a failure
*/
virtual ssize_t stop_msg_format(IWriter * writer) { return 0; }
static const char *retrieve_object_type(TABLE_LIST *pObj);
static QueryTableInf *getQueryCacheTableList1(THD *thd);
// utility functions for fetching thd stuff
static inline my_thread_id thd_inst_thread_id(THD *thd)
{
return *(my_thread_id *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.thread_id);
}
static inline query_id_t thd_inst_query_id(THD *thd)
{
return *(query_id_t *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.query_id);
}
static inline Security_context *thd_inst_main_security_ctx(THD *thd)
{
return (Security_context *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.main_security_ctx);
}
static const char * retrieve_object_type (TABLE_LIST *pObj);
static QueryTableInf* getQueryCacheTableList1 (THD *thd);
//utility functions for fetching thd stuff
static inline my_thread_id thd_inst_thread_id(THD * thd)
{
return *(my_thread_id *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.thread_id);
}
static inline query_id_t thd_inst_query_id(THD * thd)
{
return *(query_id_t *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.query_id);
}
static inline Security_context * thd_inst_main_security_ctx(THD * thd)
{
return (Security_context *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.main_security_ctx);
}
static inline const char * thd_db(THD * thd)
{
if(!Audit_formatter::thd_offsets.db) //no offsets use compiled in header
static inline const char *thd_db(THD *thd)
{
if (! Audit_formatter::thd_offsets.db) // no offsets use compiled in header
{
#if defined(MARIADB_BASE_VERSION) || MYSQL_VERSION_ID < 50709
return thd->db;
return thd->db;
#else
return thd->db().str;
#endif
}
return *(const char **) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.db);
}
return *(const char **) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.db);
}
static inline int thd_killed(THD * thd)
{
if(!Audit_formatter::thd_offsets.killed) //no offsets use thd_killed function
static inline int thd_killed(THD *thd)
{
if (! Audit_formatter::thd_offsets.killed) // no offsets use thd_killed function
{
return ::thd_killed(thd);
}
return *(int *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.killed);
}
return *(int *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.killed);
}
static inline const char * thd_inst_main_security_ctx_user(THD * thd)
{
Security_context * sctx = thd_inst_main_security_ctx(thd);
if(!Audit_formatter::thd_offsets.sec_ctx_user) //no offsets use compiled in header
static inline const char *thd_inst_main_security_ctx_user(THD *thd)
{
Security_context *sctx = thd_inst_main_security_ctx(thd);
if (! Audit_formatter::thd_offsets.sec_ctx_user) // no offsets use compiled in header
{
#if defined(MARIADB_BASE_VERSION) || MYSQL_VERSION_ID < 50709
return sctx->user;
return sctx->user;
#else
return sctx->user().str;
#endif
}
return *(const char **) (((unsigned char *) sctx)
+ Audit_formatter::thd_offsets.sec_ctx_user);
}
return *(const char **) (((unsigned char *) sctx)
+ Audit_formatter::thd_offsets.sec_ctx_user);
}
static inline const char * thd_inst_main_security_ctx_host(THD * thd)
{
Security_context * sctx = thd_inst_main_security_ctx(thd);
if(!Audit_formatter::thd_offsets.sec_ctx_ip) //check ip to understand if set as host is first and may actually be set to 0
static inline const char *thd_inst_main_security_ctx_host(THD *thd)
{
Security_context *sctx = thd_inst_main_security_ctx(thd);
if (! Audit_formatter::thd_offsets.sec_ctx_ip) // check ip to understand if set as host is first and may actually be set to 0
{
//interface changed in 5.5.34 and 5.6.14 and up host changed to get_host()
//see: http://bazaar.launchpad.net/~mysql/mysql-server/5.5/revision/4407.1.1/sql/sql_class.h
// interface changed in 5.5.34 and 5.6.14 and up host changed to get_host()
// see: http://bazaar.launchpad.net/~mysql/mysql-server/5.5/revision/4407.1.1/sql/sql_class.h
#if defined(MARIADB_BASE_VERSION)
return sctx->host;
return sctx->host;
#else
// MySQL
// MySQL
#if MYSQL_VERSION_ID < 50534 || (MYSQL_VERSION_ID >= 50600 && MYSQL_VERSION_ID < 50614)
return sctx->host;
return sctx->host;
#elif (MYSQL_VERSION_ID >= 50534 && MYSQL_VERSION_ID < 50600) \
|| (MYSQL_VERSION_ID >= 50614 && MYSQL_VERSION_ID < 50709)
return sctx->get_host()->ptr();
return sctx->get_host()->ptr();
#else
// interface changed again in 5.7
return sctx->host().str;
// interface changed again in 5.7
return sctx->host().str;
#endif
#endif // ! defined(MARIADB_BASE_VERSION)
}
return *(const char **) (((unsigned char *) sctx)
+ Audit_formatter::thd_offsets.sec_ctx_host);
}
return *(const char **) (((unsigned char *) sctx)
+ Audit_formatter::thd_offsets.sec_ctx_host);
}
static inline const char * thd_inst_main_security_ctx_ip(THD * thd)
{
Security_context * sctx = thd_inst_main_security_ctx(thd);
if(!Audit_formatter::thd_offsets.sec_ctx_ip) //no offsets use compiled in header
static inline const char *thd_inst_main_security_ctx_ip(THD *thd)
{
Security_context *sctx = thd_inst_main_security_ctx(thd);
if (! Audit_formatter::thd_offsets.sec_ctx_ip) // no offsets use compiled in header
{
//interface changed in 5.5.34 and 5.6.14 and up host changed to get_ip()
// interface changed in 5.5.34 and 5.6.14 and up host changed to get_ip()
#if defined(MARIADB_BASE_VERSION)
return sctx->ip;
return sctx->ip;
#else
// MySQL
// MySQL
#if MYSQL_VERSION_ID < 50534 || (MYSQL_VERSION_ID >= 50600 && MYSQL_VERSION_ID < 50614)
return sctx->ip;
return sctx->ip;
#elif (MYSQL_VERSION_ID >= 50534 && MYSQL_VERSION_ID < 50600) \
|| (MYSQL_VERSION_ID >= 50614 && MYSQL_VERSION_ID < 50709)
return sctx->get_ip()->ptr();
return sctx->get_ip()->ptr();
#else
// interface changed again in 5.7
return sctx->ip().str;
// interface changed again in 5.7
return sctx->ip().str;
#endif
#endif // ! defined(MARIADB_BASE_VERSION)
}
return *(const char **) (((unsigned char *) sctx)
+ Audit_formatter::thd_offsets.sec_ctx_ip);
}
return *(const char **) (((unsigned char *) sctx)
+ Audit_formatter::thd_offsets.sec_ctx_ip);
}
static inline const char * thd_inst_main_security_ctx_priv_user(THD * thd)
{
Security_context * sctx = thd_inst_main_security_ctx(thd);
if(!Audit_formatter::thd_offsets.sec_ctx_priv_user) //no offsets use compiled in header
static inline const char *thd_inst_main_security_ctx_priv_user(THD *thd)
{
Security_context *sctx = thd_inst_main_security_ctx(thd);
if (! Audit_formatter::thd_offsets.sec_ctx_priv_user) // no offsets use compiled in header
{
#if defined(MARIADB_BASE_VERSION) || MYSQL_VERSION_ID < 50709
return sctx->priv_user;
return sctx->priv_user;
#else
return sctx->priv_user().str;
#endif
}
#if MYSQL_VERSION_ID < 50505
//in 5.1.x priv_user is a pointer
// in 5.1.x priv_user is a pointer
return *(const char **) (((unsigned char *) sctx)
+ Audit_formatter::thd_offsets.sec_ctx_priv_user);
+ Audit_formatter::thd_offsets.sec_ctx_priv_user);
#else
//in 5.5 and up priv_user is an array (char priv_user[USERNAME_LENGTH])
return (const char *) (((unsigned char *) sctx)
+ Audit_formatter::thd_offsets.sec_ctx_priv_user);
// in 5.5 and up priv_user is an array (char priv_user[USERNAME_LENGTH])
return (const char *) (((unsigned char *) sctx)
+ Audit_formatter::thd_offsets.sec_ctx_priv_user);
#endif
}
}
static inline int thd_inst_command(THD * thd)
{
return *(int *) (((unsigned char *) thd) + Audit_formatter::thd_offsets.command);
}
static inline int thd_inst_command(THD *thd)
{
return *(int *) (((unsigned char *) thd) + Audit_formatter::thd_offsets.command);
}
static inline LEX* thd_lex(THD * thd)
{
return *(LEX**) (((unsigned char *) thd) + Audit_formatter::thd_offsets.lex);
}
static inline LEX *thd_lex(THD *thd)
{
return *(LEX **) (((unsigned char *) thd) + Audit_formatter::thd_offsets.lex);
}
//we don't use get_db_name() as when we call it view may be not null and it may return an invalid value for view_db
static inline const char * table_get_db_name(TABLE_LIST * table)
// we don't use get_db_name() as when we call it view may be not null and it may return an invalid value for view_db
static inline const char *table_get_db_name(TABLE_LIST *table)
{
return table->db;
}
static inline const char * table_get_name(TABLE_LIST * table)
static inline const char *table_get_name(TABLE_LIST *table)
{
return table->table_name;
}
static inline bool table_is_view(TABLE_LIST * table)
static inline bool table_is_view(TABLE_LIST *table)
{
return table->view_tables != 0;
}
};
/**
* Format the audit even in json format
*/
class Audit_json_formatter: public Audit_formatter
{
class Audit_json_formatter: public Audit_formatter {
public:
static const char *DEF_MSG_DELIMITER;
static const char * DEF_MSG_DELIMITER;
Audit_json_formatter(): m_msg_delimiter(NULL), m_write_start_msg(true), m_password_mask_regex_preg(NULL),
Audit_json_formatter(): m_msg_delimiter(NULL), m_write_start_msg(true), m_password_mask_regex_preg(NULL),
m_password_mask_regex_compiled(false), m_perform_password_masking(NULL)
{
config.beautify = 0;
config.indentString = NULL;
}
virtual ~Audit_json_formatter()
{
if(m_password_mask_regex_preg)
config.beautify = 0;
config.indentString = NULL;
}
virtual ~Audit_json_formatter()
{
if (m_password_mask_regex_preg)
{
m_password_mask_regex_compiled = false;
pcre_free(m_password_mask_regex_preg);
@ -364,20 +359,20 @@ public:
}
}
virtual ssize_t event_format(ThdSesData *pThdData, IWriter * writer);
virtual ssize_t start_msg_format(IWriter * writer);
virtual ssize_t event_format(ThdSesData *pThdData, IWriter *writer);
virtual ssize_t start_msg_format(IWriter *writer);
/**
* Utility method used to compile a regex program. Will compile and log errors if necessary.
* Return null if fails
*/
static pcre * regex_compile(const char * str);
static pcre *regex_compile(const char *str);
/**
* Compile password masking regex
* Return 0 on success
* Return true on success
*/
int compile_password_masking_regex(const char * str);
bool compile_password_masking_regex(const char *str);
/**
* Boolean indicating if to log start msg.
@ -389,20 +384,20 @@ public:
/**
* Callback function to determine if password masking should be performed
*/
my_bool (* m_perform_password_masking)(const char *cmd);
my_bool (*m_perform_password_masking)(const char *cmd);
/**
* Message delimiter. Should point to a valid json string (supporting the json escapping format).
* Will only be checked at the start. Public so can be set by sysvar.
*
* We only support a delimiter up to 32 chars
*/
char * m_msg_delimiter;
/**
* Message delimiter. Should point to a valid json string (supporting the json escapping format).
* Will only be checked at the start. Public so can be set by sysvar.
*
* We only support a delimiter up to 32 chars
*/
char *m_msg_delimiter;
/**
* Configuration of yajl. Leave public so sysvar can update this directly.
*/
yajl_gen_config config;
/**
* Configuration of yajl. Leave public so sysvar can update this directly.
*/
yajl_gen_config config;
protected:
@ -417,83 +412,79 @@ protected:
/**
* Regex used for password masking
*/
pcre * m_password_mask_regex_preg;
pcre *m_password_mask_regex_preg;
};
/**
* Base class for audit handlers. Provides basic locking setup.
*/
class Audit_handler
{
class Audit_handler {
public:
static const size_t MAX_AUDIT_HANDLERS_NUM = 4;
static const size_t JSON_FILE_HANDLER = 1;
static const size_t JSON_SOCKET_HANDLER = 3;
static Audit_handler *m_audit_handler_list[];
/**
* Will iterate the handler list and log using each handler
*/
static void log_audit_all(ThdSesData *pThdData);
static const size_t MAX_AUDIT_HANDLERS_NUM = 4;
static const size_t JSON_FILE_HANDLER = 1;
static const size_t JSON_SOCKET_HANDLER = 3;
/**
* Will iterate the handler list and stop all handlers
*/
static void stop_all();
static Audit_handler * m_audit_handler_list[];
Audit_handler() :
m_initialized(false), m_enabled(false), m_print_offset_err(true),
m_formatter(NULL), m_failed(false), m_log_io_errors(true)
{
}
/**
* Will iterate the handler list and log using each handler
*/
static void log_audit_all(ThdSesData *pThdData);
/**
* Will iterate the handler list and stop all handlers
*/
static void stop_all();
Audit_handler() :
m_initialized(false), m_enabled(false), m_print_offset_err(true), m_formatter(NULL), m_failed(false), m_log_io_errors(true)
{
}
virtual ~Audit_handler()
{
if (m_initialized)
{
rwlock_destroy(&LOCK_audit);
virtual ~Audit_handler()
{
if (m_initialized)
{
rwlock_destroy(&LOCK_audit);
pthread_mutex_destroy(&LOCK_io);
}
}
}
}
/**
* Should be called to initialize. We don't init in constructor in order to provide indication if
* pthread stuff failed init.
*
* @frmt the formatter to use in this handler (does not manage distruction of this object)
* @return 0 on success
*/
int init(Audit_formatter * frmt)
{
m_formatter = frmt;
if (m_initialized)
{
return 0;
}
int res = my_rwlock_init(&LOCK_audit, NULL);
if (res)
{
return res;
}
res = pthread_mutex_init(&LOCK_io, MY_MUTEX_INIT_SLOW);;
if (res)
{
return res;
}
m_initialized = true;
return res;
}
/**
* Should be called to initialize. We don't init in constructor in order to provide indication if
* pthread stuff failed init.
*
* @frmt the formatter to use in this handler (does not manage distruction of this object)
* @return 0 on success
*/
int init(Audit_formatter *frmt)
{
m_formatter = frmt;
if (m_initialized)
{
return 0;
}
int res = my_rwlock_init(&LOCK_audit, NULL);
if (res)
{
return res;
}
res = pthread_mutex_init(&LOCK_io, MY_MUTEX_INIT_SLOW);;
if (res)
{
return res;
}
m_initialized = true;
return res;
}
bool is_init()
{
return m_initialized;
}
bool is_init()
{
return m_initialized;
}
void set_enable(bool val);
void set_enable(bool val);
bool is_enabled()
{
@ -505,10 +496,10 @@ public:
*/
void flush();
/**
* Will get relevant shared lock and call internal method of handler
*/
void log_audit(ThdSesData *pThdData);
/**
* Will get relevant shared lock and call internal method of handler
*/
void log_audit(ThdSesData *pThdData);
/**
* Public so can be configured via sysvar
@ -516,16 +507,16 @@ public:
unsigned int m_retry_interval;
protected:
Audit_formatter * m_formatter;
virtual void handler_start();
//wiil call internal method and set failed as needed
Audit_formatter *m_formatter;
virtual void handler_start();
// wiil call internal method and set failed as needed
bool handler_start_nolock();
virtual void handler_stop();
virtual void handler_stop();
virtual bool handler_start_internal() = 0;
virtual void handler_stop_internal() = 0;
virtual bool handler_log_audit(ThdSesData *pThdData) =0;
bool m_initialized;
bool m_enabled;
virtual bool handler_log_audit(ThdSesData *pThdData) =0;
bool m_initialized;
bool m_enabled;
bool m_failed;
bool m_log_io_errors;
time_t m_last_retry_sec_ts;
@ -538,154 +529,147 @@ protected:
inline bool is_failed_now()
{
return m_failed && (m_retry_interval < 0 ||
difftime(time(NULL), m_last_retry_sec_ts) > m_retry_interval);
difftime(time(NULL), m_last_retry_sec_ts) > m_retry_interval);
}
//override default assignment and copy to protect against creating additional instances
// override default assignment and copy to protect against creating additional instances
Audit_handler & operator=(const Audit_handler&);
Audit_handler(const Audit_handler&);
private:
//bool indicating if to print offset errors to log or not
bool m_print_offset_err;
//lock io
// bool indicating if to print offset errors to log or not
bool m_print_offset_err;
// lock io
pthread_mutex_t LOCK_io;
//audit (enable) lock
// audit (enable) lock
rw_lock_t LOCK_audit;
inline void lock_shared()
{
rw_rdlock(&LOCK_audit);
}
inline void lock_exclusive()
{
rw_wrlock(&LOCK_audit);
}
inline void unlock()
{
rw_unlock(&LOCK_audit);
}
inline void lock_shared()
{
rw_rdlock(&LOCK_audit);
}
inline void lock_exclusive()
{
rw_wrlock(&LOCK_audit);
}
inline void unlock()
{
rw_unlock(&LOCK_audit);
}
};
/**
* Base class for handler which have io and need a lock
*/
class Audit_io_handler: public Audit_handler, public IWriter
{
class Audit_io_handler: public Audit_handler, public IWriter {
public:
Audit_io_handler() : m_io_dest(NULL), m_io_type(NULL)
Audit_io_handler()
: m_io_dest(NULL), m_io_type(NULL)
{
}
virtual ~Audit_io_handler()
{
}
virtual ~Audit_io_handler()
{
}
/**
* target we write to (socket/file). Public so we update via sysvar
*/
char * m_io_dest;
char *m_io_dest;
protected:
virtual bool handler_start_internal();
virtual bool handler_start_internal();
virtual void handler_stop_internal();
//used for logging messages
const char * m_io_type;
// used for logging messages
const char *m_io_type;
};
class Audit_file_handler: public Audit_io_handler
{
class Audit_file_handler: public Audit_io_handler {
public:
Audit_file_handler() :
m_sync_period(0), m_log_file(NULL), m_sync_counter(0), m_bufsize(0)
{
Audit_file_handler() :
m_sync_period(0), m_log_file(NULL), m_sync_counter(0), m_bufsize(0)
{
m_io_type = "file";
}
}
virtual ~Audit_file_handler()
{
}
virtual ~Audit_file_handler()
{
}
/**
* The period to use for syncing to the file system. 0 means we don't sync.
* 1 means each write we sync. Larger than 1 means every sync_period we sync.
*
* We leave this public so the mysql sysvar function can update this variable directly.
*/
unsigned int m_sync_period;
/**
* The period to use for syncing to the file system. 0 means we don't sync.
* 1 means each write we sync. Larger than 1 means every sync_period we sync.
*
* We leave this public so the mysql sysvar function can update this variable directly.
*/
unsigned int m_sync_period;
/**
* The buf size used by the file stream. 0 = use default, negative or 1 = no buffering
*/
long m_bufsize;
/**
* The buf size used by the file stream. 0 = use default, negative or 1 = no buffering
*/
long m_bufsize;
/**
* Write function we pass to formatter
*/
ssize_t write(const char * data, size_t size);
/**
* Write function we pass to formatter
*/
ssize_t write(const char *data, size_t size);
void close();
int open(const char * io_dest, bool m_log_errors);
//static void print_sleep (THD *thd, int delay_ms);
int open(const char *io_dest, bool m_log_errors);
// static void print_sleep(THD *thd, int delay_ms);
protected:
//override default assignment and copy to protect against creating additional instances
// override default assignment and copy to protect against creating additional instances
Audit_file_handler & operator=(const Audit_file_handler&);
Audit_file_handler(const Audit_file_handler&);
/**
* Will acquire locks and call handler_write
*/
virtual bool handler_log_audit(ThdSesData *pThdData);
FILE * m_log_file;
//the period to use for syncing
unsigned int m_sync_counter;
/**
* Will acquire locks and call handler_write
*/
virtual bool handler_log_audit(ThdSesData *pThdData);
FILE *m_log_file;
// the period to use for syncing
unsigned int m_sync_counter;
};
class Audit_socket_handler: public Audit_io_handler
{
class Audit_socket_handler: public Audit_io_handler {
public:
Audit_socket_handler() :
m_vio(NULL), m_connect_timeout(1)
{
Audit_socket_handler() :
m_vio(NULL), m_connect_timeout(1)
{
m_io_type = "socket";
}
}
virtual ~Audit_socket_handler()
{
}
virtual ~Audit_socket_handler()
{
}
/**
* Connect timeout in secconds
*/
unsigned int m_connect_timeout;
/**
* Connect timeout in secconds
*/
unsigned int m_connect_timeout;
/**
* Write function we pass to formatter
*/
ssize_t write(const char * data, size_t size);
/**
* Write function we pass to formatter
*/
ssize_t write(const char *data, size_t size);
void close();
int open(const char * io_dest, bool log_errors);
int open(const char *io_dest, bool log_errors);
protected:
//override default assignment and copy to protect against creating additional instances
// override default assignment and copy to protect against creating additional instances
Audit_socket_handler & operator=(const Audit_socket_handler&);
Audit_socket_handler(const Audit_socket_handler&);
/**
* Will acquire locks and call handler_write
*/
virtual bool handler_log_audit(ThdSesData *pThdData);
//Vio we write to
//define as void* so we don't access members directly
void * m_vio;
/**
* Will acquire locks and call handler_write
*/
virtual bool handler_log_audit(ThdSesData *pThdData);
// Vio we write to
// define as void* so we don't access members directly
void *m_vio;
};
#endif /* AUDIT_HANDLER_H_ */

4
include/md5.h Executable file → Normal file
View File

@ -43,13 +43,13 @@ extern void MD5_Init(MD5_CTX *ctx);
extern void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size);
extern void MD5_Final(unsigned char *result, MD5_CTX *ctx);
//define the my_MD5* functions
// define the my_MD5* functions
#define my_MD5Context MD5_CTX
#define my_MD5Init MD5_Init
#define my_MD5Update MD5_Update
#define my_MD5Final MD5_Final
#endif //#if MYSQL_VERSION_ID >= 50600
#endif // #if MYSQL_VERSION_ID >= 50600
#endif

View File

@ -8,8 +8,8 @@
#define MYSQL_DYNAMIC_PLUGIN 1
#define MYSQL_SERVER 1
//Fix for VIO. We don't want to using method mapping as then a change in the struct will cause the offsets compiled with to
//be wrong. As is the case with ndb which uses a version of Vio with support for ipv6 similar to 5.5 but different from 5.1
// Fix for VIO. We don't want to using method mapping as then a change in the struct will cause the offsets compiled with to
// be wrong. As is the case with ndb which uses a version of Vio with support for ipv6 similar to 5.5 but different from 5.1
#define DONT_MAP_VIO
#include <my_config.h>
@ -19,7 +19,7 @@
#include <mysql_priv.h>
#else
//version 5.5.x doesn't contain mysql_priv.h . We need to add the includes provided by it.
// version 5.5.x doesn't contain mysql_priv.h . We need to add the includes provided by it.
#if MYSQL_VERSION_ID >= 50505
// These two are not present in 5.7.9
@ -43,7 +43,7 @@
#include <sql/sql_table.h>
#include <sql/sql_view.h>
//TODO: use mysql mutex instead of pthread
// TODO: use mysql mutex instead of pthread
/*
#define pthread_mutex_lock mysql_mutex_lock
#define pthread_mutex_unlock mysql_mutex_unlock
@ -67,8 +67,8 @@
#include <my_dir.h>
#include <my_sys.h>
//5.5 use my_free with a single param. 5.1 use with 2 params
//based on: http://bazaar.launchpad.net/~mysql/myodbc/5.1/view/head:/util/stringutil.h
// 5.5 use my_free with a single param. 5.1 use with 2 params
// based on: http://bazaar.launchpad.net/~mysql/myodbc/5.1/view/head:/util/stringutil.h
#ifndef x_free
# if MYSQL_VERSION_ID >= 50500
# define x_free(A) { void *tmp= (A); if (tmp) my_free((char *) tmp); }
@ -77,23 +77,19 @@
# endif
#endif
//MariaDB doesn't have my_getsystime (returns 100 nano seconds) function. They replaced with my_hrtime_t my_hrtime() which returns microseconds
// MariaDB doesn't have my_getsystime (returns 100 nano seconds) function. They replaced with my_hrtime_t my_hrtime() which returns microseconds
#if defined(MARIADB_BASE_VERSION)
#define my_getsystime() ((my_hrtime()).val * 10)
//MariaDB has a kill service that overrides thd_killed as a macro. It also has thd_killed function defined for backwards compatibility, so we redefine it.
// MariaDB has a kill service that overrides thd_killed as a macro. It also has thd_killed function defined for backwards compatibility, so we redefine it.
#undef thd_killed
extern "C" int thd_killed(const MYSQL_THD thd);
//MariadDB 10.0.10 removed the include for thd_security_context
// MariadDB 10.0.10 removed the include for thd_security_context
#if MYSQL_VERSION_ID >= 100010
extern "C" char *thd_security_context(MYSQL_THD thd, char *buffer, unsigned int length, unsigned int max_query_len);
#endif
#endif
#endif //MYSQL_INCL_H
#endif // MYSQL_INCL_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -11,33 +11,33 @@
#define ULONG_PTR uint32_t
#endif
static const char * log_prefix = "Audit Plugin:";
static const char *log_prefix = "Audit Plugin:";
static const unsigned long PAGE_SIZE = GETPAGESIZE() ;
//used to indicate how to do the protect/unprotect
// used to indicate how to do the protect/unprotect
static bool use_exec_prot = true;
static int protect(void *addr, size_t len)
{
int res = 0;
if(use_exec_prot)
if (use_exec_prot)
{
res = mprotect(addr,len,PROT_READ|PROT_EXEC);
}
else //try doing in a 2 step fashion
else // try doing in a 2 step fashion
{
mprotect(addr,len,PROT_READ);
res = mprotect(addr,len,PROT_READ|PROT_EXEC);
}
if(res)
if (res)
{
sql_print_information(
"%s unable to protect mode: PROT_READ|PROT_EXEC. Page: %p, Size: %zu, errno: %d, res %d.",
log_prefix, (void *)addr, len, errno, res);
//fail only if nx bit is enabled
FILE * fp = fopen("/proc/cpuinfo", "r");
if(NULL == fp)
// fail only if nx bit is enabled
FILE *fp = fopen("/proc/cpuinfo", "r");
if (NULL == fp)
{
sql_print_error(
"%s unable to verify nx bit. Failed checking /proc/cpuinfo. This may happen if you have SELinux enabled. Disable SELinux execmod protection for mysqld. Page: %p, Size: %zu, errno: %d.",
@ -45,21 +45,21 @@ static int protect(void *addr, size_t len)
return res;
}
char buff[1024] = {0};
const char * flags = "flags";
const char *flags = "flags";
bool nxchecked = false;
while(fgets(buff, 1024, fp) != NULL)
while (fgets(buff, 1024, fp) != NULL)
{
char * line = buff;
//trim white space at start
char *line = buff;
// trim white space at start
while ((strlen(line) > 0) && (isspace(line[0])))
{
line++;
}
if(strncmp(line, flags, strlen(flags)) == 0)
if (strncmp(line, flags, strlen(flags)) == 0)
{
nxchecked = true;
sql_print_information("%s cpuinfo flags line: %s. ",log_prefix, line);
if(strstr(line, " nx")) //nx enabled so fail
if (strstr(line, " nx")) // nx enabled so fail
{
sql_print_error(
"%s unable to protect page and nx bit enabled. This may happen if you have SELinux enabled. Disable SELinux execmod protection for mysqld. Page: %p, Size: %zu.",
@ -71,7 +71,7 @@ static int protect(void *addr, size_t len)
}
}
fclose(fp);
if(!nxchecked) //we didn't find flags string for some reason
if (! nxchecked) // we didn't find flags string for some reason
{
sql_print_error(
"%s unable to verify nx bit. Failed finding: %s in /proc/cpuinfo. This may happen if you have SELinux enabled. Disable SELinux execmod protection for mysqld. Page: %p, Size: %zu.",
@ -82,66 +82,67 @@ static int protect(void *addr, size_t len)
return 0;
}
//will try to unprotect with PROT_READ|PROT_WRITE|PROT_EXEC. If fails (might happen under SELinux)
//will use PROT_READ|PROT_WRITE
// will try to unprotect with PROT_READ|PROT_WRITE|PROT_EXEC. If fails (might happen under SELinux)
// will use PROT_READ|PROT_WRITE
static int unprotect(void *addr, size_t len)
{
int res;
if(use_exec_prot)
if (use_exec_prot)
{
res = mprotect(addr,len,PROT_READ|PROT_WRITE|PROT_EXEC);
if(res)
res = mprotect(addr, len, PROT_READ|PROT_WRITE|PROT_EXEC);
if (res)
{
sql_print_information(
"%s unable to unprotect. Page: %p, Size: %zu, errno: %d. Using NO EXEC mode.",
log_prefix, (void *)addr, len, errno);
"%s unable to unprotect. Page: %p, Size: %zu, errno: %d. Using NO EXEC mode.",
log_prefix, (void *)addr, len, errno);
use_exec_prot = false;
//do a sanity test that we can actually unprotect/protect and that nx bit is off
// do a sanity test that we can actually unprotect/protect and that nx bit is off
res = unprotect(addr, len);
if(res)
if (res)
{
sql_print_error(
"%s unable to unprotect page. This may happen if you have SELinux enabled. Disable SELinux execmod protection for mysqld. Aborting. Page: %p, Size: %zu, errno: %d.",
log_prefix, (void *)addr, len, errno);
return res;
sql_print_error(
"%s unable to unprotect page. This may happen if you have SELinux enabled. Disable SELinux execmod protection for mysqld. Aborting. Page: %p, Size: %zu, errno: %d.",
log_prefix, (void *)addr, len, errno);
return res;
}
res = protect(addr, len);
sql_print_information("%s protect res: %d", log_prefix, res);
if(res)
{
if (res)
{
sql_print_error(
"%s unable to protect page. This may happen if you have SELinux enabled. Disable SELinux execmod protection for mysqld. Aborting. Page: %p, Size: %zu, errno: %d.",
log_prefix, (void *)addr, len, errno);
return res;
}
"%s unable to protect page. This may happen if you have SELinux enabled. Disable SELinux execmod protection for mysqld. Aborting. Page: %p, Size: %zu, errno: %d.",
log_prefix, (void *)addr, len, errno);
return res;
}
}
else //all is good
else // all is good
{
return res;
}
}
res = mprotect(addr,len,PROT_READ|PROT_WRITE);
if(0 != res) //log the failure
res = mprotect(addr, len, PROT_READ|PROT_WRITE);
if (0 != res) // log the failure
{
sql_print_error(
"%s unable to unprotect. Page: %p, Size: %zu, errno: %d. Error.",
log_prefix, (void *)addr, len, errno);
"%s unable to unprotect. Page: %p, Size: %zu, errno: %d. Error.",
log_prefix, (void *)addr, len, errno);
}
return res;
}
//macro to log via sql_print_information only if cond test is enabled
#define cond_info_print(cond_test, ...) do{if(cond_test) sql_print_information(__VA_ARGS__);}while(0)
// macro to log via sql_print_information only if cond test is enabled
#define cond_info_print(cond_test, ...) do { if (cond_test) sql_print_information(__VA_ARGS__);} while (0)
/*
* Get the page address of a given pointer
*/
static DATATYPE_ADDRESS get_page_address(void * pointer)
static DATATYPE_ADDRESS get_page_address(void *pointer)
{
DATATYPE_ADDRESS pageMask = ( ~(PAGE_SIZE - 1) ) ;
DATATYPE_ADDRESS longp = (unsigned long) pointer;
return (longp & pageMask);
return (longp & pageMask);
}
//
@ -164,112 +165,115 @@ unsigned int jump_size()
static void WriteJump(void *pAddress, ULONG_PTR JumpTo)
{
DATATYPE_ADDRESS AddressPage = get_page_address(pAddress);
unprotect((void*)AddressPage, PAGE_SIZE);
DATATYPE_ADDRESS AddressPage = get_page_address(pAddress);
unprotect((void*)AddressPage, PAGE_SIZE);
BYTE *pCur = (BYTE *) pAddress;
BYTE *pCur = (BYTE *) pAddress;
#ifndef __x86_64__
BYTE * pbJmpSrc = pCur + 5;
*pCur++ = 0xE9; // jmp +imm32
*((ULONG_PTR *)pCur) = JumpTo - (ULONG_PTR)pbJmpSrc;
*pCur++ = 0xE9; // jmp +imm32
*((ULONG_PTR *)pCur) = JumpTo - (ULONG_PTR)pbJmpSrc;
#else
*pCur = 0xff; // jmp [rip+addr]
*(++pCur) = 0x25;
*((DWORD *) ++pCur) = 0; // addr = 0
pCur += sizeof (DWORD);
*((ULONG_PTR *)pCur) = JumpTo;
*pCur = 0xff; // jmp [rip+addr]
*(++pCur) = 0x25;
*((DWORD *) ++pCur) = 0; // addr = 0
pCur += sizeof (DWORD);
*((ULONG_PTR *)pCur) = JumpTo;
#endif
//}
// DWORD dwBuf = 0; // necessary othewrise the function fails
//DWORD dwBuf = 0; // nessary othewrise the function fails
protect((void*)AddressPage, PAGE_SIZE);
protect((void*)AddressPage, PAGE_SIZE);
}
//
// Hooks a function
//
static bool HookFunction(ULONG_PTR targetFunction, ULONG_PTR newFunction, ULONG_PTR trampolineFunction,
static bool HookFunction(ULONG_PTR targetFunction, ULONG_PTR newFunction, ULONG_PTR trampolineFunction,
unsigned int *trampolinesize)
{
#define MAX_INSTRUCTIONS 100
uint8_t raw[MAX_INSTRUCTIONS];
unsigned int uCurrentSize =0;
#define MAX_INSTRUCTIONS 100
uint8_t raw[MAX_INSTRUCTIONS];
unsigned int uCurrentSize =0;
#ifndef __x86_64__
#define ASM_MODE 32
#define ASM_MODE 32
#else
#define ASM_MODE 64
#define ASM_MODE 64
#endif
memcpy (raw,(void*)targetFunction,MAX_INSTRUCTIONS);
ud_t ud_obj;
ud_init(&ud_obj);
ud_set_input_buffer(&ud_obj, raw, MAX_INSTRUCTIONS);
ud_set_mode(&ud_obj, ASM_MODE);
ud_set_syntax(&ud_obj, UD_SYN_INTEL);
ud_set_pc(&ud_obj, targetFunction);
memcpy(raw, (void*)targetFunction, MAX_INSTRUCTIONS);
ud_t ud_obj;
ud_init(&ud_obj);
ud_set_input_buffer(&ud_obj, raw, MAX_INSTRUCTIONS);
ud_set_mode(&ud_obj, ASM_MODE);
ud_set_syntax(&ud_obj, UD_SYN_INTEL);
ud_set_pc(&ud_obj, targetFunction);
DWORD InstrSize = 0;
DATATYPE_ADDRESS trampolineFunctionPage = get_page_address((void*)trampolineFunction);
if(unprotect((void*)trampolineFunctionPage, PAGE_SIZE) != 0)
DWORD InstrSize = 0;
DATATYPE_ADDRESS trampolineFunctionPage = get_page_address((void*)trampolineFunction);
if (unprotect((void*)trampolineFunctionPage, PAGE_SIZE) != 0)
{
sql_print_error(
"%s unable to unprotect trampoline function page: %p. Aborting.",
log_prefix, (void *)trampolineFunctionPage);
"%s unable to unprotect trampoline function page: %p. Aborting.",
log_prefix, (void *)trampolineFunctionPage);
return false;
}
bool disassemble_valid = false;
while (ud_disassemble(&ud_obj))
{
if(ud_obj.mnemonic == UD_Iinvalid)
{
sql_print_error(
"%s unable to disassemble at address: %p. Aborting.",
log_prefix, (void *)(InstrSize + targetFunction));
break;
}
//make sure there isn't a jmp/call (or similar operand) as these use
//relative addressing and if we copy as is we will mess up the jmp/call target
if(ud_obj.mnemonic == UD_Ijmp || ud_obj.mnemonic == UD_Icall ||
ud_obj.operand[0].type == UD_OP_JIMM)
{
sql_print_error(
"%s unable to disassemble at address: 0x%p. Found relative addressing for instruction: [%s]. Aborting.",
log_prefix, (void *)(InstrSize + targetFunction), ud_insn_asm(&ud_obj));
break;
}
BYTE *pCurInstr = (BYTE *) (InstrSize + (ULONG_PTR) targetFunction);
memcpy((BYTE*)trampolineFunction + uCurrentSize,
(void *) pCurInstr, ud_insn_len (&ud_obj));
bool disassemble_valid = false;
while (ud_disassemble(&ud_obj))
{
if (ud_obj.mnemonic == UD_Iinvalid)
{
sql_print_error(
"%s unable to disassemble at address: %p. Aborting.",
log_prefix, (void *)(InstrSize + targetFunction));
break;
}
uCurrentSize += ud_insn_len (&ud_obj);
InstrSize += ud_insn_len (&ud_obj);
if (InstrSize >= jump_size()) //we have enough space so break
{
disassemble_valid = true;
break;
}
}
if(protect((void*)trampolineFunctionPage, PAGE_SIZE)) //0 valid return
// make sure there isn't a jmp/call (or similar operand) as these use
// relative addressing and if we copy as is we will mess up the jmp/call target
if (ud_obj.mnemonic == UD_Ijmp || ud_obj.mnemonic == UD_Icall ||
ud_obj.operand[0].type == UD_OP_JIMM)
{
sql_print_error(
"%s unable to disassemble at address: 0x%p. Found relative addressing for instruction: [%s]. Aborting.",
log_prefix, (void *)(InstrSize + targetFunction), ud_insn_asm(&ud_obj));
break;
}
BYTE *pCurInstr = (BYTE *) (InstrSize + (ULONG_PTR) targetFunction);
memcpy((BYTE*)trampolineFunction + uCurrentSize,
(void *) pCurInstr, ud_insn_len (&ud_obj));
uCurrentSize += ud_insn_len (&ud_obj);
InstrSize += ud_insn_len (&ud_obj);
if (InstrSize >= jump_size()) // we have enough space so break
{
disassemble_valid = true;
break;
}
}
if (protect((void*)trampolineFunctionPage, PAGE_SIZE)) // 0 valid return
{
sql_print_error(
"%s unable to protect page. Error. Page: %p.",
log_prefix, (void *)trampolineFunctionPage);
"%s unable to protect page. Error. Page: %p.",
log_prefix, (void *)trampolineFunctionPage);
return false;
}
if(!disassemble_valid) //something went wrong. log was written before so return false
{
return false;
}
WriteJump( (BYTE*)trampolineFunction + uCurrentSize, targetFunction + InstrSize);
WriteJump((void *) targetFunction, newFunction);
*trampolinesize = uCurrentSize;
return true;
if (! disassemble_valid) // something went wrong. log was written before so return false
{
return false;
}
WriteJump((BYTE*)trampolineFunction + uCurrentSize, targetFunction + InstrSize);
WriteJump((void *) targetFunction, newFunction);
*trampolinesize = uCurrentSize;
return true;
}
//
@ -277,18 +281,18 @@ static bool HookFunction(ULONG_PTR targetFunction, ULONG_PTR newFunction, ULONG
//
static void UnhookFunction(ULONG_PTR Function,ULONG_PTR trampolineFunction , unsigned int trampolinesize)
static void UnhookFunction(ULONG_PTR Function, ULONG_PTR trampolineFunction, unsigned int trampolinesize)
{
DATATYPE_ADDRESS FunctionPage = get_page_address((void*)Function);
if(unprotect((void*)FunctionPage, PAGE_SIZE) != 0)
DATATYPE_ADDRESS FunctionPage = get_page_address((void*)Function);
if (unprotect((void*)FunctionPage, PAGE_SIZE) != 0)
{
sql_print_error(
"%s Unhook not able to unprotect function page: %p. Aborting.",
log_prefix, (void * )FunctionPage);
"%s Unhook not able to unprotect function page: %p. Aborting.",
log_prefix, (void * )FunctionPage);
return;
}
memcpy((void *) Function, (void*)trampolineFunction,trampolinesize);
protect((void*)FunctionPage, PAGE_SIZE);
memcpy((void *) Function, (void*)trampolineFunction,trampolinesize);
protect((void*)FunctionPage, PAGE_SIZE);
}
/**
@ -306,21 +310,21 @@ static void UnhookFunction(ULONG_PTR Function,ULONG_PTR trampolineFunction , uns
* which contains a bunch of nops.
* @param info_print if true will print info as progressing
* @Return 0 on success otherwise failure
* @See MS Detours paper: http://research.microsoft.com/pubs/68568/huntusenixnt99.pdf for some background info.
* @See MS Detours paper: http:// research.microsoft.com/pubs/68568/huntusenixnt99.pdf for some background info.
*/
int hot_patch_function (void* targetFunction, void* newFunction, void * trampolineFunction, unsigned int *trampolinesize, bool info_print)
int hot_patch_function(void *targetFunction, void *newFunction, void *trampolineFunction, unsigned int *trampolinesize, bool info_print)
{
DATATYPE_ADDRESS trampolinePage = get_page_address(trampolineFunction);
cond_info_print(info_print, "%s hot patching function: %p, trampolineFunction: %p trampolinePage: %p",log_prefix, (void *)targetFunction, (void *)trampolineFunction, (void *)trampolinePage);
if (HookFunction((ULONG_PTR) targetFunction, (ULONG_PTR) newFunction,
(ULONG_PTR) trampolineFunction, trampolinesize))
{
return 0;
}
else
{
return -1;
}
if (HookFunction((ULONG_PTR) targetFunction, (ULONG_PTR) newFunction,
(ULONG_PTR) trampolineFunction, trampolinesize))
{
return 0;
}
else
{
return -1;
}
}
@ -332,11 +336,11 @@ int hot_patch_function (void* targetFunction, void* newFunction, void * trampoli
* @param trampolineFunction a function which contains a jump back to the targetFunction.
* @param log_file if not null will log about progress of installing the plugin
*/
void remove_hot_patch_function (void* targetFunction, void * trampolineFunction, unsigned int trampolinesize, bool info_print)
void remove_hot_patch_function(void *targetFunction, void *trampolineFunction, unsigned int trampolinesize, bool info_print)
{
if(trampolinesize == 0)
if (trampolinesize == 0)
{
//nothing todo. As hot patch was not set.
// nothing todo. As hot patch was not set.
return;
}
DATATYPE_ADDRESS targetPage = get_page_address(targetFunction);

0
src/md5.cc Executable file → Normal file
View File