Add info about process on other end of UNIX Domain Socket and

also client port of remote connection for TCP Socket. New offset
required for this, so updated the offsets as well.
pull/179/head
Arnold Robbins 2017-01-02 11:52:28 +02:00
parent d83c36627c
commit 4fbcae3a81
5 changed files with 1062 additions and 715 deletions

View File

@ -48,10 +48,23 @@ typedef struct _THDPRINTED {
char is_thd_printed_queue[MAX_NUM_QUEUE_ELEM];
} THDPRINTED;
#define MAX_COMMAND_CHAR_NUMBERS 40
struct PeerInfo {
unsigned long pid;
enum { MAX_APP_NAME_LEN = 128, MAX_USER_NAME_LEN = 128 };
char appName[MAX_APP_NAME_LEN + 1];
char osUser[MAX_USER_NAME_LEN + 1]; // allow lots, in case from LDAP or some such
PeerInfo() : pid(0) {
memset(appName, 0, sizeof appName);
memset(osUser, 0, sizeof osUser);
}
};
PeerInfo *retrieve_peerinfo(THD *thd);
const char *retrieve_command(THD *thd, bool& is_sql_cmd);
typedef size_t OFFSET;
#define MAX_COMMAND_CHAR_NUMBERS 40
#define MAX_COM_STATUS_VARS_RECORDS 512
// mysql max identifier is 64 so 2*64 + . and null
@ -82,6 +95,7 @@ typedef struct ThdOffsets {
OFFSET pfs_connect_attrs;
OFFSET pfs_connect_attrs_length;
OFFSET pfs_connect_attrs_cs;
OFFSET net;
} ThdOffsets;
/*
@ -115,10 +129,14 @@ 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; }
THD *getTHD() const { return m_pThd;}
const char *getCmdName() const { return m_CmdName; }
void setCmdName(const char *cmd) { m_CmdName = cmd; }
const char *getUserName() { return m_UserName; }
const unsigned long getPeerPid() const;
const char *getAppName() const;
const char *getOsUser() const;
const int getPort() const { return m_port; }
/**
* Start fetching objects. Return true if there are objects available.
*/
@ -145,6 +163,10 @@ private:
QueryTableInf *m_tableInf;
int m_index;
PeerInfo *m_peerInfo;
int m_port; // TCP port of remote side
protected:
ThdSesData(const ThdSesData&);
ThdSesData &operator =(const ThdSesData&);
@ -415,6 +437,50 @@ static inline const CHARSET_INFO * pfs_connect_attrs_cs(void * pfs)
return *(const CHARSET_INFO **) (((unsigned char *) pfs) + Audit_formatter::thd_offsets.pfs_connect_attrs_cs);
}
}
static inline int thd_client_fd(THD *thd)
{
if (! Audit_formatter::thd_offsets.net)
{
return -1;
}
NET *net = ((NET *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.net));
// get the socket for the peer
int sock = -1;
if (net->vio != NULL) // MySQL 5.7.17 - this can happen. :-(
{
#if MYSQL_VERSION_ID < 50600
sock = net->vio->sd;
#else
sock = net->vio->mysql_socket.fd;
#endif
}
return sock;
}
static inline int thd_client_port(THD *thd)
{
if (! Audit_formatter::thd_offsets.net)
{
return -1;
}
NET *net = ((NET *) (((unsigned char *) thd)
+ Audit_formatter::thd_offsets.net));
// get the port for the remote end
int port = -1;
if (net->vio != NULL) // MySQL 5.7.17 - this can happen. :-(
{
struct sockaddr_in *in;
in = (struct sockaddr_in *) & net->vio->remote;
port = in->sin_port;
}
return port;
}
// 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
@ -446,7 +512,8 @@ public:
: m_msg_delimiter(NULL),
m_write_start_msg(true),
m_write_sess_connect_attrs(true),
m_write_client_capabilities(true),
m_write_client_capabilities(false),
m_write_socket_creds(true),
m_password_mask_regex_preg(NULL),
m_password_mask_regex_compiled(false),
m_perform_password_masking(NULL)
@ -498,6 +565,11 @@ public:
*/
my_bool m_write_client_capabilities;
/**
* include socket credentials from Unix Domain Socket
* Public for sysvar
*/
my_bool m_write_socket_creds;
/**
* Callback function to determine if password masking should be performed

View File

@ -92,6 +92,7 @@ print_offset THD $DB
print_offset THD killed
$CLIENT_CAPS
$CONNECT_ATTRS
print_offset THD net
printf "}"
EOF

View File

@ -23,6 +23,8 @@
// for definition of sockaddr_un
#include <sys/un.h>
#include <stdio_ext.h>
#include <limits.h>
#include <unistd.h>
#include "static_assert.h"
// utility macro to log also with a date as a prefix
@ -621,6 +623,7 @@ ssize_t Audit_json_formatter::start_msg_format(IWriter *writer)
yajl_add_string_val(gen, "mysql-program", my_progname);
yajl_add_string_val(gen, "mysql-socket", mysqld_unix_port);
yajl_add_uint64(gen, "mysql-port", mysqld_port);
yajl_add_uint64(gen, "server_pid", getpid());
ssize_t res = -2;
yajl_gen_status stat = yajl_gen_map_close(gen); // close the object
@ -786,12 +789,14 @@ ssize_t Audit_json_formatter::event_format(ThdSesData *pThdData, IWriter *writer
yajl_add_string_val(gen, "priv_user", Audit_formatter::thd_inst_main_security_ctx_priv_user(thd));
yajl_add_string_val(gen, "ip", Audit_formatter::thd_inst_main_security_ctx_ip(thd));
// Don't send host unless there's a real value
// For backwards compatibility, we always send "host".
// If there is no value, send the IP address
const char *host = Audit_formatter::thd_inst_main_security_ctx_host(thd);
if (host != NULL && *host != '\0')
if (host == NULL || *host == '\0')
{
yajl_add_string_val(gen, "host", host);
host = Audit_formatter::thd_inst_main_security_ctx_ip(thd);
}
yajl_add_string_val(gen, "host", host);
if (m_write_client_capabilities)
{
@ -809,6 +814,26 @@ ssize_t Audit_json_formatter::event_format(ThdSesData *pThdData, IWriter *writer
}
#endif
if (pThdData->getPeerPid() != 0) // Unix Domain Socket
{
if (m_write_socket_creds)
{
yajl_add_uint64(gen, "pid", pThdData->getPeerPid());
if (pThdData->getOsUser() != NULL)
{
yajl_add_string_val(gen, "os_user", pThdData->getOsUser());
}
if (pThdData->getAppName() != NULL)
{
yajl_add_string_val(gen, "appname", pThdData->getAppName());
}
}
}
else if (pThdData->getPort() > 0) // TCP socket
{
yajl_add_uint64(gen, "client_port", pThdData->getPort());
}
const char *cmd = pThdData->getCmdName();
yajl_add_string_val(gen, "cmd", cmd);
@ -938,10 +963,18 @@ ssize_t Audit_json_formatter::event_format(ThdSesData *pThdData, IWriter *writer
ThdSesData::ThdSesData(THD *pTHD)
: m_pThd (pTHD), m_CmdName(NULL), m_UserName(NULL),
m_objIterType(OBJ_NONE), m_tables(NULL), m_firstTable(true),
m_tableInf(NULL), m_index(0), m_isSqlCmd(false)
m_tableInf(NULL), m_index(0), m_isSqlCmd(false),
m_port(-1)
{
m_CmdName = retrieve_command (m_pThd, m_isSqlCmd);
m_UserName = retrieve_user (m_pThd);
m_peerInfo = retrieve_peerinfo(m_pThd);
if (m_peerInfo && m_peerInfo->pid == 0)
{
// not UDS, get remote port
m_port = Audit_formatter::thd_client_port(m_pThd);
}
}
bool ThdSesData::startGetObjects()
@ -1055,6 +1088,21 @@ bool ThdSesData::getNextObject(const char **db_name, const char **obj_name, cons
}
}
const unsigned long ThdSesData::getPeerPid() const
{
return (m_peerInfo != NULL ? m_peerInfo->pid : 0L);
}
const char *ThdSesData::getAppName() const
{
return (m_peerInfo != NULL ? m_peerInfo->appName : NULL);
}
const char *ThdSesData::getOsUser() const
{
return (m_peerInfo != NULL ? m_peerInfo->osUser : NULL);
}
pcre *Audit_json_formatter::regex_compile(const char *str)
{
const char *error;

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
#include "hot_patch.h"
#include <stdlib.h>
#include <ctype.h>
#include <pwd.h>
#include "audit_handler.h"
#include <string.h>
@ -152,6 +153,32 @@ static MYSQL_THDVAR_ULONG(query_cache_table_list,
#endif
1);
static MYSQL_THDVAR_BOOL(set_peer_cred,
PLUGIN_VAR_READONLY | PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
"track if peer credential information is set",
NULL, NULL, 0);
static MYSQL_THDVAR_BOOL(peer_is_uds,
PLUGIN_VAR_READONLY | PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
"track if client is connected on Unix Domain Socket",
NULL, NULL, 0);
// We use a THDVAR_STR to store the PeerInfo struct for this thread.
// In order to get MySQL to allocate storage correctly, we have to
// fool it into thinking that the storage holds a C string. To do so,
// we create a char array of the right size and use that as the initial
// value. Then in the constructor routines, below, we initialize the
// contents to look like a C string with a bunch of '0' characters,
// terminated by a final '\0'.
char peer_info_init_value[sizeof(struct PeerInfo) + 4];
static MYSQL_THDVAR_STR(peer_info,
PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_READONLY | /* PLUGIN_VAR_NOCMDOPT | */ PLUGIN_VAR_MEMALLOC,
"Info related to client process",
NULL, NULL, peer_info_init_value);
THDPRINTED *GetThdPrintedList(THD *thd)
{
THDPRINTED *pThdPrintedList= (THDPRINTED*) THDVAR(thd, is_thd_printed_list);
@ -163,6 +190,153 @@ THDPRINTED *GetThdPrintedList(THD *thd)
return NULL;
}
static void initializePeerCredentials(THD *pThd)
{
int sock;
struct stat sbuf;
struct ucred cred;
socklen_t cred_len = sizeof(cred);
char buf[BUFSIZ];
struct passwd pwd, *pwbuf = NULL;
char *username = NULL;
int fd;
PeerInfo *peer;
// zero out thread local copy of PeerInfo
peer = (PeerInfo *) THDVAR(pThd, peer_info);
if (peer != NULL)
{
memset(peer, 0, sizeof(PeerInfo));
}
// get the NET structure
sock = Audit_formatter::thd_client_fd(pThd);
// These tests are not chained so that we can add
// debug prints if necessary.
if (sock < 0)
{
goto done;
}
// Is it a Unix Domain socket?
if (fstat(sock, &sbuf) < 0)
{
goto done;
}
if ((sbuf.st_mode & S_IFMT) != S_IFSOCK)
{
goto done;
}
// At this point, we know we have a Unix domain socket.
if (! json_formatter.m_write_socket_creds)
{
// We need to set this, so that we don't send the
// client port, but we don't bother getting the command
// name and user name, since they won't be sent.
THDVAR(pThd, peer_is_uds) = TRUE;
goto done;
}
// Do a SO_PEERCRED
if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) < 0)
{
goto done;
}
if (cred_len != sizeof(cred))
{
goto done;
}
if (peer == NULL)
{
goto done;
}
// Set PID
peer->pid = cred.pid;
// Get OS user name
getpwuid_r(cred.uid, & pwd, buf, sizeof(buf), & pwbuf);
if (pwbuf == NULL)
{
// no name, send UID
snprintf(buf, sizeof(buf) - 1, "%lu", (unsigned long) cred.uid);
username = buf;
}
else
{
username = pwd.pw_name;
}
strncpy(peer->osUser, username, PeerInfo::MAX_USER_NAME_LEN);
peer->osUser[PeerInfo::MAX_USER_NAME_LEN] = '\0';
// Get the progran name out of /proc
snprintf(buf, sizeof(buf) - 1, "/proc/%d/cmdline", cred.pid);
fd = open(buf, O_RDONLY);
if (fd >= 0)
{
char data[PATH_MAX+1];
ssize_t count = read(fd, data, sizeof(data) - 1);
if (count > 0)
{
data[count] = '\0'; // just in case
if (strlen(data) <= PeerInfo::MAX_APP_NAME_LEN)
{
strcpy(peer->appName, data);
}
else
{
// take last 128 characters, typically will be
// .../x/y/appname
char *cp = data + strlen(data) - PeerInfo::MAX_APP_NAME_LEN;
strncpy(peer->appName, cp, PeerInfo::MAX_APP_NAME_LEN);
peer->appName[PeerInfo::MAX_APP_NAME_LEN] = '\0';
}
}
close(fd);
}
// in case of errors:
if (peer->appName[0] == '\0')
{
snprintf(peer->appName, sizeof(peer->appName) - 1, "pid:%d", cred.pid);
}
// set that we have a UDS, so that THD vars will be used
THDVAR(pThd, peer_is_uds) = TRUE;
done:
THDVAR(pThd, set_peer_cred) = TRUE;
}
PeerInfo *retrieve_peerinfo(THD *thd)
{
if (! THDVAR(thd, set_peer_cred))
{
initializePeerCredentials(thd);
}
if (json_formatter.m_write_socket_creds)
{
PeerInfo *peer = (PeerInfo *) THDVAR(thd, peer_info);
if (THDVAR(thd, peer_is_uds) && peer != NULL);
{
return peer;
}
}
return NULL;
}
static int check_array(const char *cmds[],const char *array, int length)
{
for (int k = 0; array[k * length] !='\0';k++)
@ -1045,7 +1219,7 @@ static int setup_offsets()
if (parse_thd_offsets_string(offsets_string))
{
sql_print_information ("%s setup_offsets Audit_formatter::thd_offsets values: %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu", log_prefix,
sql_print_information ("%s setup_offsets Audit_formatter::thd_offsets values: %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu", log_prefix,
Audit_formatter::thd_offsets.query_id,
Audit_formatter::thd_offsets.thread_id,
Audit_formatter::thd_offsets.main_security_ctx,
@ -1061,7 +1235,8 @@ static int setup_offsets()
Audit_formatter::thd_offsets.client_capabilities,
Audit_formatter::thd_offsets.pfs_connect_attrs,
Audit_formatter::thd_offsets.pfs_connect_attrs_length,
Audit_formatter::thd_offsets.pfs_connect_attrs_cs
Audit_formatter::thd_offsets.pfs_connect_attrs_cs,
Audit_formatter::thd_offsets.net
);
if (! validate_offsets(&Audit_formatter::thd_offsets))
@ -1152,10 +1327,14 @@ static int setup_offsets()
{
decoffsets.client_capabilities -= dec;
}
if(decoffsets.net)
{
decoffsets.net -= dec;
}
if (validate_offsets(&decoffsets))
{
Audit_formatter::thd_offsets = decoffsets;
sql_print_information("%s Using decrement (%zu) offsets from offset version: %s (%s) values: %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu",
sql_print_information("%s Using decrement (%zu) offsets from offset version: %s (%s) values: %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu",
log_prefix, dec, offset->version, offset->md5digest,
Audit_formatter::thd_offsets.query_id,
Audit_formatter::thd_offsets.thread_id,
@ -1168,7 +1347,11 @@ static int setup_offsets()
Audit_formatter::thd_offsets.sec_ctx_ip,
Audit_formatter::thd_offsets.sec_ctx_priv_user,
Audit_formatter::thd_offsets.killed,
Audit_formatter::thd_offsets.client_capabilities);
Audit_formatter::thd_offsets.client_capabilities,
Audit_formatter::thd_offsets.pfs_connect_attrs,
Audit_formatter::thd_offsets.pfs_connect_attrs_length,
Audit_formatter::thd_offsets.pfs_connect_attrs_cs,
Audit_formatter::thd_offsets.net);
DBUG_RETURN(0);
}
@ -1185,7 +1368,7 @@ static int setup_offsets()
if (validate_offsets(&incoffsets))
{
Audit_formatter::thd_offsets = incoffsets;
sql_print_information("%s Using increment (%zu) offsets from offset version: %s (%s) values: %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu",
sql_print_information("%s Using increment (%zu) offsets from offset version: %s (%s) values: %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu",
log_prefix, inc, offset->version, offset->md5digest,
Audit_formatter::thd_offsets.query_id,
Audit_formatter::thd_offsets.thread_id,
@ -1196,7 +1379,13 @@ static int setup_offsets()
Audit_formatter::thd_offsets.sec_ctx_user,
Audit_formatter::thd_offsets.sec_ctx_host,
Audit_formatter::thd_offsets.sec_ctx_ip,
Audit_formatter::thd_offsets.sec_ctx_priv_user);
Audit_formatter::thd_offsets.sec_ctx_priv_user,
Audit_formatter::thd_offsets.killed,
Audit_formatter::thd_offsets.client_capabilities,
Audit_formatter::thd_offsets.pfs_connect_attrs,
Audit_formatter::thd_offsets.pfs_connect_attrs_length,
Audit_formatter::thd_offsets.pfs_connect_attrs_cs,
Audit_formatter::thd_offsets.net);
DBUG_RETURN(0);
}
}
@ -1227,11 +1416,14 @@ const char *retrieve_command(THD *thd, bool &is_sql_cmd)
}
const int sql_command = thd_sql_command(thd);
#if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100108
if (command == COM_QUERY && sql_command >= 0 && sql_command < SQLCOM_END)
#define MAX_SQL_COM_INDEX SQLCOM_END
#else
if (command != COM_STMT_PREPARE && sql_command >= 0 && sql_command < MAX_COM_STATUS_VARS_RECORDS)
#define MAX_SQL_COM_INDEX MAX_COM_STATUS_VARS_RECORDS
#endif
if (command != COM_STMT_PREPARE && sql_command >= 0 && sql_command < MAX_SQL_COM_INDEX)
{
is_sql_cmd = true;
cmd = com_status_vars_array[sql_command].name;
@ -1621,6 +1813,7 @@ static int audit_plugin_init(void *p)
log_prefix, MYSQL_AUDIT_PLUGIN_VERSION,
MYSQL_AUDIT_PLUGIN_REVISION, arch, interface_ver, interface_ver,
server_version);
// setup our offsets.
if (setup_offsets() != 0)
@ -1643,6 +1836,12 @@ static int audit_plugin_init(void *p)
log_prefix);
}
#endif
if (!Audit_formatter::thd_offsets.net)
{
sql_print_error("%s offset for NET structure not defined. peer attributes will not be logged.",
log_prefix);
}
if (delay_cmds_string != NULL)
{
delay_cmds_string_update(NULL, NULL, NULL, &delay_cmds_string);
@ -1840,6 +2039,8 @@ static int audit_plugin_init(void *p)
{
DBUG_RETURN(1);
}
sql_print_information("%s Init completed successfully.", log_prefix);
DBUG_RETURN(0);
}
@ -1923,6 +2124,10 @@ static void json_log_socket_enable(THD *thd, struct st_mysql_sys_var *var,
// setup sysvars which update directly the relevant plugins
static MYSQL_SYSVAR_BOOL(socket_creds, json_formatter.m_write_socket_creds,
PLUGIN_VAR_RQCMDARG,
"AUDIT log socket credentials from Unix Domain Socket. Enable|Disable. Default enabled.", NULL, NULL, 1);
static MYSQL_SYSVAR_BOOL(client_capabilities, json_formatter.m_write_client_capabilities,
PLUGIN_VAR_RQCMDARG,
"AUDIT log client capabilities. Enable|Disable. Default disabled.", NULL, NULL, 0);
@ -2059,6 +2264,7 @@ static struct st_mysql_sys_var* audit_system_variables[] =
#ifdef HAVE_SESS_CONNECT_ATTRS
MYSQL_SYSVAR(sess_connect_attrs),
#endif
MYSQL_SYSVAR(socket_creds),
MYSQL_SYSVAR(client_capabilities),
MYSQL_SYSVAR(header_msg),
MYSQL_SYSVAR(force_record_logins),
@ -2087,6 +2293,9 @@ static struct st_mysql_sys_var* audit_system_variables[] =
MYSQL_SYSVAR(record_objs),
MYSQL_SYSVAR(checksum),
MYSQL_SYSVAR(password_masking_regex),
MYSQL_SYSVAR(set_peer_cred),
MYSQL_SYSVAR(peer_is_uds),
MYSQL_SYSVAR(peer_info),
NULL
};
@ -2130,6 +2339,9 @@ extern "C" void __attribute__ ((constructor)) audit_plugin_so_init(void)
"%s mysqld_builtins are null. Plugin will not load unless the mysql version is: %d. \n",
log_prefix, audit_plugin.interface_version >> 8);
}
memset(peer_info_init_value, '0', sizeof(peer_info_init_value)-1);
peer_info_init_value[sizeof(peer_info_init_value) - 1] = '\0';
}
#elif MYSQL_VERSION_ID < 50600
extern struct st_mysql_plugin *mysql_mandatory_plugins[];
@ -2140,6 +2352,8 @@ extern "C" void __attribute__ ((constructor)) audit_plugin_so_init(void)
log_prefix, audit_plugin.interface_version,
audit_plugin.interface_version >> 8);
memset(peer_info_init_value, '0', sizeof(peer_info_init_value)-1);
peer_info_init_value[sizeof(peer_info_init_value) - 1] = '\0';
}
#elif !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID < 50709
// Interface version for MySQL 5.6 changed in 5.6.14.
@ -2155,6 +2369,16 @@ extern "C" void __attribute__ ((constructor)) audit_plugin_so_init(void)
{
audit_plugin.interface_version = 0x0301;
}
memset(peer_info_init_value, '0', sizeof(peer_info_init_value)-1);
peer_info_init_value[sizeof(peer_info_init_value) - 1] = '\0';
}
#else
// 5.7
extern "C" void __attribute__ ((constructor)) audit_plugin_so_init(void)
{
memset(peer_info_init_value, '0', sizeof(peer_info_init_value)-1);
peer_info_init_value[sizeof(peer_info_init_value) - 1] = '\0';
}
#endif