Initial Sysdig syscall (event) support.

Add a dissector for reading Sysdig event blocks. It only handles plain
events but it's usable for reading trace files on hand here.

Use a script to generate various parts of the dissector. As an experiment,
update parts in-place instead of using a template.

Ultimately there should probably be a top-level "Syscall" or "Event"
dissector alongside the "Frame" dissector, which could then call this.
You could then directly compare an executable's system calls alongside
its network traffic.

For now leverage the pcapng_block dissector and keep everything under
"Frame".

Next steps:
- Items listed at the top of packet-sysdig-event.c.

Change-Id: I17077e8d7f40d10a946d61189ebc077d81c4da37
Reviewed-on: https://code.wireshark.org/review/11103
Petri-Dish: Gerald Combs <gerald@wireshark.org>
Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Gerald Combs <gerald@wireshark.org>
daniel/osmux
Gerald Combs 2015-06-01 19:48:23 -07:00 committed by Gerald Combs
parent 6a56224c18
commit 0d497e8125
8 changed files with 2685 additions and 5 deletions

View File

@ -1321,6 +1321,7 @@ set(DISSECTOR_SRC
dissectors/packet-sync.c
dissectors/packet-synergy.c
dissectors/packet-synphasor.c
dissectors/packet-sysdig-event.c
dissectors/packet-sysex.c
dissectors/packet-syslog.c
dissectors/packet-t30.c

View File

@ -187,6 +187,7 @@ ASN1_DISSECTOR_SRC = \
packet-smrse.c \
packet-spnego.c \
packet-sv.c \
packet-sysdig-event.c \
packet-sysex.c \
packet-t124.c \
packet-t125.c \

File diff suppressed because it is too large Load Diff

View File

@ -137,6 +137,7 @@ EXTRA_DIST = \
fuzz-test.sh \
gen-bugnote \
generate-bacnet-vendors.py \
generate-sysdig-event.py \
Get-HardenFlags.ps1 \
git-compare-abis.sh \
git-export-release.sh \

345
tools/generate-sysdig-event.py Executable file
View File

@ -0,0 +1,345 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Wireshark - Network traffic analyzer
# By Gerald Combs <gerald@wireshark.org>
# Copyright 1998 Gerald Combs
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
'''\
Generate Sysdig event dissector sections from the sysdig sources.
Reads driver/event_table.c and driver/ppm_events_public.h and generates
corresponding dissection code in packet-sysdig-event.c. Updates are
performed in-place in the dissector code.
Requires an Internet connection. Assets are loaded from GitHub over HTTPS.
'''
import os
import os.path
import re
import urllib2
import sys
sysdig_repo_pfx = 'https://raw.githubusercontent.com/draios/sysdig/0.5.0/'
ppm_ev_pub = urllib2.urlopen(sysdig_repo_pfx + 'driver/ppm_events_public.h')
ppm_ev_pub_lines = ppm_ev_pub.readlines()
ppm_ev_pub.close()
ppme_re = re.compile('^\s+PPME_([A-Z0-9_]+_[EX])\s*=\s*([0-9]+)\s*,')
event_info_d = {}
def get_event_defines():
event_d = {}
for line in ppm_ev_pub_lines:
m = ppme_re.match(line)
if m:
event_d[int(m.group(2))] = m.group(1)
return event_d
ppm_ev_table = urllib2.urlopen(sysdig_repo_pfx + 'driver/event_table.c')
ppm_ev_table_lines = ppm_ev_table.readlines()
ppm_ev_table.close()
hf_d = {}
event_info_re = re.compile('^\s+/\*\s*PPME_.*\*\/\s*{\s*"([A-Za-z0-9_]+)"\s*,[^,]+,[^,]+,\s*([0-9]+)\s*[,{}]')
event_param_re = re.compile('{\s*"([A-Za-z0-9_]+)"\s*,\s*PT_([A-Z0-9_]+)\s*,\s*PF_([A-Z0-9_]+)\s*[,}]')
def get_event_names():
'''Return a contiguous list of event names. Names are lower case.'''
event_name_l = []
for line in ppm_ev_table_lines:
ei = event_info_re.match(line)
if ei:
event_name_l.append(ei.group(1))
return event_name_l
# PT_xxx to FT_xxx
pt_to_ft = {
'BYTEBUF': 'BYTES',
'CHARBUF': 'STRING',
'FD': 'INT64',
'FSPATH': 'STRING',
}
def get_event_params():
'''Return a list of dictionaries containing event names and parameter info.'''
event_param_l = []
event_num = 0
force_string_l = ['args', 'env']
for line in ppm_ev_table_lines:
ei = event_info_re.match(line)
ep = event_param_re.findall(line)
if ei and ep:
src_param_count = int(ei.group(2))
if len(ep) != src_param_count:
err_msg = '{}: found {} parameters. Expected {}. Params: {}'.format(
ei.group(1), len(ep), src_param_count, repr(ep))
raise NameError(err_msg)
for p in ep:
if p[0] in force_string_l:
param_type = 'STRING'
elif p[1] in pt_to_ft:
param_type = pt_to_ft[p[1]]
elif p[0] == 'flags' and p[1].startswith('INT') and 'HEX' in p[2]:
param_type = 'U' + p[1]
elif 'INT' in p[1]:
# Ints
param_type = p[1]
else:
# Fall back to bytes
param_type = 'BYTES'
if p[2] == 'NA':
if 'INT' in param_type:
param_format = 'DEC'
else:
param_format = 'NONE'
elif param_type == 'BYTES':
param_format = 'NONE'
else:
param_format = p[2]
param_d = {
'event_name': ei.group(1),
'event_num': event_num,
'param_name': p[0],
'param_type': param_type,
'param_format': param_format,
}
event_param_l.append(param_d)
if ei:
event_num += 1
return event_param_l
def param_to_hf_name(param):
return 'hf_param_{}_{}'.format(param['param_name'], param['param_type'].lower())
def param_to_value_string_name(param):
return '{}_{}_vals'.format(param['param_name'], param['param_type'].lower())
def get_param_desc(param):
# Try to coerce event names and parameters into human-friendly
# strings.
# XXX This could use some work.
# Specific descriptions. Event name + parameter name.
param_descs = {
'accept.queuepct': 'Accept queue per connection',
'execve.args': 'Program arguments',
'execve.comm': 'Command',
'execve.cwd': 'Current working directory',
}
# General descriptions. Event name only.
event_descs = {
'ioctl': 'I/O control',
}
event_name = param['event_name']
param_id = '{}.{}'.format(event_name, param['param_name'])
if param_id in param_descs:
param_desc = param_descs[param_id]
elif event_name in event_descs:
param_desc = '{}: {}'.format(event_descs[event_name], param['param_name'])
else:
param_desc = param['param_name']
return param_desc
def main():
# Event list
event_d = get_event_defines()
event_nums = event_d.keys()
event_nums.sort()
event_name_l = get_event_names()
event_param_l = get_event_params()
hf_d = {}
for param in event_param_l:
hf_name = param_to_hf_name(param)
hf_d[hf_name] = param
idx_id_to_name = { '': 'no' }
parameter_index_l = []
for en in range (0, len(event_nums)):
param_id = ''
param_l = []
event_var = event_d[en].lower()
for param in event_param_l:
if param['event_num'] == en:
hf_name = param_to_hf_name(param)
param_l.append(hf_name)
param_id += ':' + param['param_name'] + '_' + param['param_type']
ei_str = ''
if param_id not in idx_id_to_name:
idx_id_to_name[param_id] = event_var
ei_str = 'static const int *{}_indexes[] = {{ &{}, NULL }};'.format(
event_var,
', &'.join(param_l)
)
else:
ei_str = '#define {}_indexes {}_indexes'.format(event_var, idx_id_to_name[param_id])
parameter_index_l.append(ei_str)
dissector_path = os.path.join(os.path.dirname(__file__),
'..', 'epan', 'dissectors', 'packet-sysdig-event.c')
dissector_f = open(dissector_path, 'r')
dissector_lines = list(dissector_f)
dissector_f = open(dissector_path, 'w+')
# Strip out old content
strip_re_l = []
strip_re_l.append(re.compile('^static\s+int\s+hf_param_.*;'))
strip_re_l.append(re.compile('^#define\s+EVT_STR_[A-Z0-9_]+\s+"[A-Za-z0-9_]+"'))
strip_re_l.append(re.compile('^#define\s+EVT_[A-Z0-9_]+\s+[0-9]+'))
strip_re_l.append(re.compile('^\s*{\s*EVT_[A-Z0-9_]+\s*,\s*EVT_STR_[A-Z0-9_]+\s*}'))
strip_re_l.append(re.compile('^static\s+const\s+int\s+\*\s*[a-z0-9_]+_[ex]_indexes\[\]\s*=\s*\{\s*&hf_param_.*NULL\s*\}\s*;'))
strip_re_l.append(re.compile('^\s*#define\s+[a-z0-9_]+_[ex]_indexes\s+[a-z0-9_]+_indexes'))
strip_re_l.append(re.compile('^\s*\{\s*EVT_[A-Z0-9_]+_[EX]\s*,\s*[a-z0-9_]+_[ex]_indexes\s*}\s*,'))
strip_re_l.append(re.compile('^\s*{\s*&hf_param_.*},')) # Must all be on one line
for strip_re in strip_re_l:
dissector_lines = [l for l in dissector_lines if not strip_re.search(l)]
# Find our value strings
value_string_re = re.compile('static\s+const\s+value_string\s+([A-Za-z0-9_]+_vals)')
value_string_l = []
for line in dissector_lines:
vs = value_string_re.match(line)
if vs:
value_string_l.append(vs.group(1))
# Add in new content after comments.
header_fields_c = 'Header fields'
header_fields_re = re.compile('/\*\s+' + header_fields_c, flags = re.IGNORECASE)
header_fields_l = []
for hf_name in sorted(hf_d.keys()):
header_fields_l.append('static int {} = -1;'.format(hf_name))
event_names_c = 'Event names'
event_names_re = re.compile('/\*\s+' + event_names_c, flags = re.IGNORECASE)
event_names_l = []
event_str_l = list(set(event_name_l))
event_str_l.sort()
for evt_str in event_str_l:
event_names_l.append('#define EVT_STR_{0:24s} "{1:s}"'.format(evt_str.upper(), evt_str))
event_definitions_c = 'Event definitions'
event_definitions_re = re.compile('/\*\s+' + event_definitions_c, flags = re.IGNORECASE)
event_definitions_l = []
for evt in event_nums:
event_definitions_l.append('#define EVT_{0:24s} {1:3d}'.format(event_d[evt], evt))
value_strings_c = 'Value strings'
value_strings_re = re.compile('/\*\s+' + value_strings_c, flags = re.IGNORECASE)
value_strings_l = []
for evt in event_nums:
evt_num = 'EVT_{},'.format(event_d[evt])
evt_str = 'EVT_STR_' + event_name_l[evt].upper()
value_strings_l.append(' {{ {0:<32s} {1:s} }},'.format(evt_num, evt_str))
parameter_index_c = 'Parameter indexes'
parameter_index_re = re.compile('/\*\s+' + parameter_index_c, flags = re.IGNORECASE)
# parameter_index_l defined above.
event_tree_c = 'Event tree'
event_tree_re = re.compile('/\*\s+' + event_tree_c, flags = re.IGNORECASE)
event_tree_l = []
for evt in event_nums:
evt_num = 'EVT_{}'.format(event_d[evt])
evt_idx = '{}_indexes'.format(event_d[evt].lower())
event_tree_l.append(' {{ {}, {} }},'.format(evt_num, evt_idx))
header_field_reg_c = 'Header field registration'
header_field_reg_re = re.compile('/\*\s+' + header_field_reg_c, flags = re.IGNORECASE)
header_field_reg_l = []
for hf_name in sorted(hf_d.keys()):
param = hf_d[hf_name]
event_name = param['event_name']
param_desc = get_param_desc(param)
param_name = param['param_name']
param_type = param['param_type']
param_format = param['param_format']
fieldconvert = 'NULL'
vs_name = param_to_value_string_name(param)
if vs_name in value_string_l and 'INT' in param_type:
fieldconvert = 'VALS({})'.format(vs_name)
header_field_reg_l.append(' {{ &{}, {{ "{}", "sysdig.param.{}.{}", FT_{}, BASE_{}, {}, 0, NULL, HFILL }} }},'.format(
hf_name,
param_desc,
event_name,
param_name,
param_type,
param_format,
fieldconvert
))
for line in dissector_lines:
fill_comment = None
fill_l = []
if header_fields_re.match(line):
fill_comment = header_fields_c
fill_l = header_fields_l
elif event_names_re.match(line):
fill_comment = event_names_c
fill_l = event_names_l
elif event_definitions_re.match(line):
fill_comment = event_definitions_c
fill_l = event_definitions_l
elif value_strings_re.match(line):
fill_comment = value_strings_c
fill_l = value_strings_l
elif parameter_index_re.match(line):
fill_comment = parameter_index_c
fill_l = parameter_index_l
elif event_tree_re.match(line):
fill_comment = event_tree_c
fill_l = event_tree_l
elif header_field_reg_re.match(line):
fill_comment = header_field_reg_c
fill_l = header_field_reg_l
if fill_comment is not None:
# Write our comment followed by the content
print('Generating {}, {:d} lines'.format(fill_comment, len(fill_l)))
dissector_f.write('/* {}. Automatically generated by tools/{} */\n'.format(
fill_comment,
os.path.basename(__file__)
))
for line in fill_l:
dissector_f.write('{}\n'.format(line))
# Fill each section only once
del fill_l[:]
else:
# Existing content
dissector_f.write(line)
dissector_f.close()
#
# On with the show
#
if __name__ == "__main__":
sys.exit(main())

View File

@ -183,6 +183,11 @@ typedef struct pcapng_interface_statistics_block_s {
*/
#define MIN_ISB_SIZE ((guint32)(MIN_BLOCK_SIZE + sizeof(pcapng_interface_statistics_block_t)))
/*
* Minimum Sysdig size = minimum block size + packed size of sysdig_event_phdr.
*/
#define MIN_SYSDIG_EVENT_SIZE ((guint32)(MIN_BLOCK_SIZE)) + ((16 + 64 + 64 + 32 + 16) / 8)
/* pcapng: common option header file encoding for every option type */
typedef struct pcapng_option_header_s {
guint16 option_code;
@ -196,7 +201,6 @@ struct option {
guint16 value_length;
};
/* Option codes: 16-bit field */
#define OPT_EOFOPT 0x0000
#define OPT_COMMENT 0x0001
@ -378,8 +382,9 @@ register_pcapng_block_type_handler(guint block_type, block_reader reader,
#define BT_INDEX_PBS 2 /* all packet blocks */
#define BT_INDEX_NRB 3
#define BT_INDEX_ISB 4
#define BT_INDEX_EVT 5
#define NUM_BT_INDICES 5
#define NUM_BT_INDICES 6
static GHashTable *option_handlers[NUM_BT_INDICES];
@ -412,6 +417,11 @@ get_block_type_index(guint block_type, guint *bt_index)
*bt_index = BT_INDEX_ISB;
break;
case BLOCK_TYPE_SYSDIG_EVENT:
/* case BLOCK_TYPE_SYSDIG_EVF: */
*bt_index = BT_INDEX_EVT;
break;
default:
/*
* This is a block type we don't process; either we ignore it,
@ -2155,6 +2165,96 @@ pcapng_read_interface_statistics_block(FILE_T fh, pcapng_block_header_t *bh, pca
return TRUE;
}
static gboolean
pcapng_read_sysdig_event_block(FILE_T fh, pcapng_block_header_t *bh, pcapng_t *pn _U_, wtapng_block_t *wblock, int *err, gchar **err_info)
{
unsigned block_read;
guint32 block_total_length;
guint16 cpu_id;
guint64 wire_ts;
guint64 ts;
guint64 thread_id;
guint32 event_len;
guint16 event_type;
if (bh->block_total_length < MIN_SYSDIG_EVENT_SIZE) {
*err = WTAP_ERR_BAD_FILE;
*err_info = g_strdup_printf("%s: total block length %u is too small (< %u)", G_STRFUNC,
bh->block_total_length, MIN_SYSDIG_EVENT_SIZE);
return FALSE;
}
/* add padding bytes to "block total length" */
/* (the "block total length" of some example files don't contain any padding bytes!) */
if (bh->block_total_length % 4) {
block_total_length = bh->block_total_length + 4 - (bh->block_total_length % 4);
} else {
block_total_length = bh->block_total_length;
}
pcapng_debug("pcapng_read_sysdig_event_block: block_total_length %u",
bh->block_total_length);
wblock->packet_header->rec_type = REC_TYPE_FT_SPECIFIC_EVENT;
wblock->packet_header->pseudo_header.sysdig_event.record_type = BLOCK_TYPE_SYSDIG_EVENT;
wblock->packet_header->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN /*|WTAP_HAS_INTERFACE_ID */;
wblock->packet_header->pkt_tsprec = WTAP_TSPREC_NSEC;
block_read = block_total_length;
if (!wtap_read_bytes(fh, &cpu_id, sizeof cpu_id, err, err_info)) {
pcapng_debug("pcapng_read_packet_block: failed to read sysdig event cpu id");
return FALSE;
}
if (!wtap_read_bytes(fh, &wire_ts, sizeof wire_ts, err, err_info)) {
pcapng_debug("pcapng_read_packet_block: failed to read sysdig event timestamp");
return FALSE;
}
if (!wtap_read_bytes(fh, &thread_id, sizeof thread_id, err, err_info)) {
pcapng_debug("pcapng_read_packet_block: failed to read sysdig event thread id");
return FALSE;
}
if (!wtap_read_bytes(fh, &event_len, sizeof event_len, err, err_info)) {
pcapng_debug("pcapng_read_packet_block: failed to read sysdig event length");
return FALSE;
}
if (!wtap_read_bytes(fh, &event_type, sizeof event_type, err, err_info)) {
pcapng_debug("pcapng_read_packet_block: failed to read sysdig event type");
return FALSE;
}
block_read -= MIN_SYSDIG_EVENT_SIZE;
wblock->packet_header->pseudo_header.sysdig_event.byte_order = G_BYTE_ORDER;
if (pn->byte_swapped) {
wblock->packet_header->pseudo_header.sysdig_event.byte_order =
G_BYTE_ORDER == G_LITTLE_ENDIAN ? G_BIG_ENDIAN : G_LITTLE_ENDIAN;
wblock->packet_header->pseudo_header.sysdig_event.cpu_id = GUINT16_SWAP_LE_BE(cpu_id);
ts = GUINT64_SWAP_LE_BE(wire_ts);
wblock->packet_header->pseudo_header.sysdig_event.thread_id = GUINT64_SWAP_LE_BE(thread_id);
wblock->packet_header->pseudo_header.sysdig_event.event_len = GUINT32_SWAP_LE_BE(event_len);
wblock->packet_header->pseudo_header.sysdig_event.event_type = GUINT16_SWAP_LE_BE(event_type);
} else {
wblock->packet_header->pseudo_header.sysdig_event.cpu_id = cpu_id;
ts = wire_ts;
wblock->packet_header->pseudo_header.sysdig_event.thread_id = thread_id;
wblock->packet_header->pseudo_header.sysdig_event.event_len = event_len;
wblock->packet_header->pseudo_header.sysdig_event.event_type = event_type;
}
wblock->packet_header->ts.secs = (time_t) (ts / 1000000000);
wblock->packet_header->ts.nsecs = (int) (ts % 1000000000);
wblock->packet_header->caplen = block_read;
wblock->packet_header->len = wblock->packet_header->pseudo_header.sysdig_event.event_len;
/* "Sysdig Event Block" read event data */
if (!wtap_read_packet_bytes(fh, wblock->frame_buffer,
block_read, err, err_info))
return FALSE;
return TRUE;
}
static gboolean
pcapng_read_unknown_block(FILE_T fh, pcapng_block_header_t *bh, pcapng_t *pn _U_, wtapng_block_t *wblock _U_, int *err, gchar **err_info)
@ -2290,6 +2390,11 @@ pcapng_read_block(wtap *wth, FILE_T fh, pcapng_t *pn, wtapng_block_t *wblock, in
if (!pcapng_read_interface_statistics_block(fh, &bh, pn, wblock, err, err_info))
return PCAPNG_BLOCK_ERROR;
break;
case(BLOCK_TYPE_SYSDIG_EVENT):
/* case(BLOCK_TYPE_SYSDIG_EVF): */
if (!pcapng_read_sysdig_event_block(fh, &bh, pn, wblock, err, err_info))
return PCAPNG_BLOCK_ERROR;
break;
default:
pcapng_debug("pcapng_read_block: Unknown block_type: 0x%x (block ignored), block total length %d", bh.block_type, bh.block_total_length);
if (!pcapng_read_unknown_block(fh, &bh, pn, wblock, err, err_info))
@ -2523,6 +2628,8 @@ pcapng_read(wtap *wth, int *err, gchar **err_info, gint64 *data_offset)
case(BLOCK_TYPE_PB):
case(BLOCK_TYPE_SPB):
case(BLOCK_TYPE_EPB):
case(BLOCK_TYPE_SYSDIG_EVENT):
case(BLOCK_TYPE_SYSDIG_EVF):
/* packet block - we've found a packet */
goto got_packet;
@ -2615,10 +2722,11 @@ pcapng_seek_read(wtap *wth, gint64 seek_off,
}
/* block must be a "Packet Block", an "Enhanced Packet Block",
or a "Simple Packet Block" */
a "Simple Packet Block", or an event */
if (wblock.type != BLOCK_TYPE_PB && wblock.type != BLOCK_TYPE_EPB &&
wblock.type != BLOCK_TYPE_SPB) {
pcapng_debug("pcapng_seek_read: block type %u not PB/EPB/SPB", wblock.type);
wblock.type != BLOCK_TYPE_SPB &&
wblock.type != BLOCK_TYPE_SYSDIG_EVENT && wblock.type != BLOCK_TYPE_SYSDIG_EVF) {
pcapng_debug("pcapng_seek_read: block type %u not PB/EPB/SPB", wblock.type);
return FALSE;
}

View File

@ -28,6 +28,8 @@
#define BLOCK_TYPE_NRB 0x00000004 /* Name Resolution Block */
#define BLOCK_TYPE_ISB 0x00000005 /* Interface Statistics Block */
#define BLOCK_TYPE_EPB 0x00000006 /* Enhanced Packet Block */
#define BLOCK_TYPE_SYSDIG_EVENT 0x00000204 /* Sysdig Event Block */
#define BLOCK_TYPE_SYSDIG_EVF 0x00000208 /* Sysdig Event Block with flags */
#define BLOCK_TYPE_SHB 0x0A0D0D0A /* Section Header Block */
/* TODO: the following are not yet well defined in the draft spec:
* Compression Block

View File

@ -1119,6 +1119,20 @@ struct logcat_phdr {
gint version;
};
/* Packet "pseudo-header" information for Sysdig events. */
struct sysdig_event_phdr {
guint record_type; /* XXX match ft_specific_record_phdr so that we chain off of packet-pcapng_block for now. */
int byte_order;
guint16 cpu_id;
/* guint32 sentinel; */
guint64 timestamp; /* ns since epoch */
guint64 thread_id;
guint32 event_len; /* XXX dup of wtap_pkthdr.len */
guint16 event_type;
/* ... Event ... */
};
/* Pseudo-header for file-type-specific records */
struct ft_specific_record_phdr {
guint record_type; /* the type of record this is */
@ -1150,6 +1164,7 @@ union wtap_pseudo_header {
struct nokia_phdr nokia;
struct llcp_phdr llcp;
struct logcat_phdr logcat;
struct sysdig_event_phdr sysdig_event;
struct ft_specific_record_phdr ftsrec;
};