Skip to content
This repository was archived by the owner on Feb 10, 2023. It is now read-only.

Commit

Permalink
Merge pull request percona#2015 from dutow/lp1716844-ps56
Browse files Browse the repository at this point in the history
lp1716844: Escaping control characters in the audit log
  • Loading branch information
dutow authored Dec 21, 2017
2 parents b4ec7d0 + fde057f commit 8f7b145
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 31 deletions.
36 changes: 36 additions & 0 deletions mysql-test/include/audit_log_events.inc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,42 @@ CREATE TABLE t1
(c1 INT,
c2 CHAR(20));
INSERT INTO t1 VALUES (1,'a'),(2,'b'),(3,'c');
-- if ($test_control_chars) {
INSERT INTO `t1` VALUES (4,NULL);
# can't add the zero ascii character, as that's a syntax error in MySQL
INSERT INTO `t1` VALUES (6,'');
INSERT INTO `t1` VALUES (7,'');
INSERT INTO `t1` VALUES (8,'');
INSERT INTO `t1` VALUES (9,'');
INSERT INTO `t1` VALUES (10,'');
INSERT INTO `t1` VALUES (11,'');
INSERT INTO `t1` VALUES (12,'');
INSERT INTO `t1` VALUES (13,'');
INSERT INTO `t1` VALUES (14,' ');
INSERT INTO `t1` VALUES (15,'
');
INSERT INTO `t1` VALUES (16,' ');
INSERT INTO `t1` VALUES (17,' ');
INSERT INTO `t1` VALUES (18,'');
INSERT INTO `t1` VALUES (19,'');
INSERT INTO `t1` VALUES (20,'');
INSERT INTO `t1` VALUES (21,'');
INSERT INTO `t1` VALUES (22,'');
INSERT INTO `t1` VALUES (23,'');
INSERT INTO `t1` VALUES (24,'');
INSERT INTO `t1` VALUES (25,'');
INSERT INTO `t1` VALUES (26,'');
INSERT INTO `t1` VALUES (27,'');
INSERT INTO `t1` VALUES (28,'');
INSERT INTO `t1` VALUES (29,'');
INSERT INTO `t1` VALUES (30,'');
INSERT INTO `t1` VALUES (31,'');
INSERT INTO `t1` VALUES (32,'');
INSERT INTO `t1` VALUES (33,'');
INSERT INTO `t1` VALUES (34,'');
INSERT INTO `t1` VALUES (35,'');
INSERT INTO `t1` VALUES (36,'');
-- }
SELECT * FROM t1;
--error ER_NO_SUCH_TABLE
SELECT * FROM t2;
Expand Down
66 changes: 66 additions & 0 deletions mysql-test/r/audit_log_json.result
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,77 @@ CREATE TABLE t1
c2 CHAR(20));
ERROR 42S01: Table 't1' already exists
INSERT INTO t1 VALUES (1,'a'),(2,'b'),(3,'c');
INSERT INTO `t1` VALUES (4,NULL);
INSERT INTO `t1` VALUES (6,'');
INSERT INTO `t1` VALUES (7,'');
INSERT INTO `t1` VALUES (8,'');
INSERT INTO `t1` VALUES (9,'');
INSERT INTO `t1` VALUES (10,'');
INSERT INTO `t1` VALUES (11,'');
INSERT INTO `t1` VALUES (12,'');
INSERT INTO `t1` VALUES (13,'');
INSERT INTO `t1` VALUES (14,' ');
INSERT INTO `t1` VALUES (15,'
');
INSERT INTO `t1` VALUES (16,' ');
INSERT INTO `t1` VALUES (17,' ');
INSERT INTO `t1` VALUES (18,'');
INSERT INTO `t1` VALUES (19,'');
INSERT INTO `t1` VALUES (20,'');
INSERT INTO `t1` VALUES (21,'');
INSERT INTO `t1` VALUES (22,'');
INSERT INTO `t1` VALUES (23,'');
INSERT INTO `t1` VALUES (24,'');
INSERT INTO `t1` VALUES (25,'');
INSERT INTO `t1` VALUES (26,'');
INSERT INTO `t1` VALUES (27,'');
INSERT INTO `t1` VALUES (28,'');
INSERT INTO `t1` VALUES (29,'');
INSERT INTO `t1` VALUES (30,'');
INSERT INTO `t1` VALUES (31,'');
INSERT INTO `t1` VALUES (32,'');
INSERT INTO `t1` VALUES (33,'');
INSERT INTO `t1` VALUES (34,'');
INSERT INTO `t1` VALUES (35,'');
INSERT INTO `t1` VALUES (36,'');
SELECT * FROM t1;
c1 c2
1 a
2 b
3 c
4 NULL
6 
7 
8 
9 
10 
11 
12 
13 
14
15

16
17
18
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
SELECT * FROM t2;
ERROR 42S02: Table 'test.t2' doesn't exist
DROP TABLE t1;
Expand Down
30 changes: 27 additions & 3 deletions mysql-test/t/audit_log_json.test
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,41 @@ SET GLOBAL audit_log_flush=ON;
--remove_file $MYSQLD_DATADIR/test_audit.log
SET GLOBAL audit_log_flush=ON;

--let $test_control_chars=1;
--source include/audit_log_events.inc

--move_file $MYSQLD_DATADIR/test_audit.log $MYSQLD_DATADIR/test_audit_json.log
set global audit_log_flush= ON;
perl;
eval "use JSON qw(decode_json); 1" or exit 0;
open my $file, $ENV{'MYSQLD_DATADIR'} . '/test_audit_json.log' or die "Could not open log: $!";
my $found_1st_control_char = 0;
my $last_control_char = 0;
my $control_char_count = 0;
while (my $line = <$file>) {
decode_json($line);
my $json = decode_json($line);
my $entry_type = $json->{audit_record}->{name};
if($entry_type eq "Query") {
my $query = $json->{audit_record}->{sqltext};
my @query_chars = sort($query =~ /./sg);
my $minimum_character = ord($query_chars[0]);
if ($minimum_character == 1) {
$found_1st_control_char = 1;
}
if ($found_1st_control_char && $control_char_count < 31) {
$control_char_count = $control_char_count + 1;
my $expected = $last_control_char + 1;
if ($expected != $minimum_character) {
print "Incorrect control character in output: Expected $expected, got $minimum_character\n";
exit l;
}
$last_control_char = $minimum_character;
}
}
}
if ($control_char_count != 31) {
print "Missing control characters from the output. Expected 31, got $control_char_count\n";
exit 2;
}
close $file;
EOF
--remove_file $MYSQLD_DATADIR/test_audit.log
--remove_file $MYSQLD_DATADIR/test_audit_json.log
170 changes: 142 additions & 28 deletions plugin/audit_log/audit_log.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,88 +172,202 @@ typedef struct

static
void escape_buf(const char *in, size_t *inlen, char *out, size_t *outlen,
const escape_rule_t *escape_rules)
const escape_rule_t *control_escape_rules,
const escape_rule_t *other_escape_rules)
{
char* outstart = out;
const char* base = in;
char* outend = out + *outlen;
const char* inend;
const escape_rule_t *rule;
my_bool replaced;
const escape_rule_t *replace_rule = NULL;

inend = in + (*inlen);

while ((in < inend) && (out < outend))
{
replaced= FALSE;
for (rule= escape_rules; rule->character; rule++)
replace_rule = NULL;
if ((unsigned char)(*in) < 32) {
if (control_escape_rules[(unsigned int)*in].character) {
replace_rule = &control_escape_rules[(unsigned int)*in];
}
} else
{
if (*in == rule->character)
const escape_rule_t *rule = NULL;
for (rule= other_escape_rules; rule->character; rule++)
{
if ((outend - out) < (int) rule->length)
goto end_of_buffer;
memcpy(out, rule->replacement, rule->length);
out += rule->length;
replaced= TRUE;
break;
if (*in == rule->character)
{
replace_rule = rule;
break;
}
}
}
if (!replaced)
if (replace_rule)
{
if ((outend - out) < (ptrdiff_t) replace_rule->length)
break;
memcpy(out, replace_rule->replacement, replace_rule->length);
out += replace_rule->length;
} else
{
*out++ = *in;
}
++in;
}
end_of_buffer:
*outlen = out - outstart;
*inlen = in - base;
}

static
void xml_escape(const char *in, size_t *inlen, char *out, size_t *outlen)
{
const escape_rule_t rules[]=
// Most control sequences aren't supported before XML 1.1, and most
// tools only support 1.0. Our output is 1.0. Escaping them wouldn't make
// the output more valid.
static const escape_rule_t control_rules[]=
{
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ '\t', 5, "&#9;" },
{ '\n', 6, "&#10;" },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ '\r', 6, "&#13;" },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
};
static const escape_rule_t other_rules[]=
{
{ '<', 4, "&lt;" },
{ '>', 4, "&gt;" },
{ '&', 5, "&amp;" },
{ '\r', 5, "&#13;" },
{ '\n', 5, "&#10;" },
{ '\t', 5, "&#9;" },
{ '"', 6, "&quot;" },
{ 0, 0, NULL }
};

escape_buf(in, inlen, out, outlen, rules);
escape_buf(in, inlen, out, outlen, control_rules, other_rules);
}

static
void json_escape(const char *in, size_t *inlen, char *out, size_t *outlen)
{
const escape_rule_t rules[]=
static const escape_rule_t control_rules[]=
{
{ 0, 6, "\\u0000" },
{ 1, 6, "\\u0001" },
{ 2, 6, "\\u0002" },
{ 3, 6, "\\u0003" },
{ 4, 6, "\\u0004" },
{ 5, 6, "\\u0005" },
{ 6, 6, "\\u0006" },
{ 7, 6, "\\u0007" },
{ '\b', 2, "\\b" },
{ '\t', 2, "\\t" },
{ '\n', 2, "\\n" },
{ 11, 6, "\\u000B" },
{ '\f', 2, "\\f" },
{ '\r', 2, "\\r" },
{ 14, 6, "\\u000E" },
{ 15, 6, "\\u000F" },
{ 16, 6, "\\u0010" },
{ 17, 6, "\\u0011" },
{ 18, 6, "\\u0012" },
{ 19, 6, "\\u0013" },
{ 20, 6, "\\u0014" },
{ 21, 6, "\\u0015" },
{ 22, 6, "\\u0016" },
{ 23, 6, "\\u0017" },
{ 24, 6, "\\u0018" },
{ 25, 6, "\\u0019" },
{ 26, 6, "\\u001A" },
{ 27, 6, "\\u001B" },
{ 28, 6, "\\u001C" },
{ 29, 6, "\\u001D" },
{ 30, 6, "\\u001E" },
{ 31, 6, "\\u001F" },
};

static const escape_rule_t other_rules[]=
{
{ '\\', 2, "\\\\" },
{ '"', 2, "\\\"" },
{ '\r', 2, "\\r" },
{ '\n', 2, "\\n" },
{ '/', 2, "\\/" },
{ '\b', 2, "\\b" },
{ '\f', 2, "\\f" },
{ '\t', 2, "\\t" },
{ 0, 0, NULL }
};

escape_buf(in, inlen, out, outlen, rules);
escape_buf(in, inlen, out, outlen, control_rules, other_rules);
}

static
void csv_escape(const char *in, size_t *inlen, char *out, size_t *outlen)
{
const escape_rule_t rules[]=
// We do not have any standard control escape rules for CSVs
static const escape_rule_t control_rules[]=
{
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
{ 0, 0, NULL },
};

static const escape_rule_t other_rules[]=
{
{ '"', 2, "\"\"" },
{ 0, 0, NULL }
};

escape_buf(in, inlen, out, outlen, rules);
escape_buf(in, inlen, out, outlen, control_rules, other_rules);
}

static const escape_buf_func_t format_escape_func[]=
Expand Down

0 comments on commit 8f7b145

Please sign in to comment.