Coverage Report

Created: 2026-03-30 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/wiretap/systemd_journal.c
Line
Count
Source
1
/* systemd_journal.c
2
 *
3
 * Wiretap Library
4
 * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
5
 *
6
 * SPDX-License-Identifier: GPL-2.0-or-later
7
 */
8
9
#include "config.h"
10
#include "systemd_journal.h"
11
12
#include <errno.h>
13
#include <string.h>
14
#include <stdlib.h>
15
16
#include <wsutil/pint.h>
17
18
#include "wtap_module.h"
19
#include "pcapng_module.h"
20
#include "file_wrappers.h"
21
22
// To do:
23
// - Request a pcap encapsulation type.
24
// - Should we add separate types for binary, plain, and JSON or add a metadata header?
25
26
// Systemd journals are stored in the following formats:
27
// Journal File Format (native binary): https://www.freedesktop.org/wiki/Software/systemd/journal-files/
28
// Journal Export Format: https://www.freedesktop.org/wiki/Software/systemd/export/
29
// Journal JSON format: https://www.freedesktop.org/wiki/Software/systemd/json/
30
// This reads Journal Export Format files but could be extended to support
31
// the binary and JSON formats.
32
33
// Example data:
34
// __CURSOR=s=1d56bab64d414960b9907ab0cc7f7c62;i=2;b=1497926e8b4b4d3ca6a5805e157fa73c;m=5d0ae5;t=56f2f5b66ce6f;x=20cb01e28bb496a8
35
// __REALTIME_TIMESTAMP=1529624071163503
36
// __MONOTONIC_TIMESTAMP=6097637
37
// _BOOT_ID=1497926e8b4b4d3ca6a5805e157fa73c
38
// PRIORITY=6
39
// _MACHINE_ID=62c342838a6e436dacea041aa4b5064b
40
// _HOSTNAME=example.wireshark.org
41
// _SOURCE_MONOTONIC_TIMESTAMP=0
42
// _TRANSPORT=kernel
43
// SYSLOG_FACILITY=0
44
// SYSLOG_IDENTIFIER=kernel
45
// MESSAGE=Initializing cgroup subsys cpuset
46
47
static bool systemd_journal_read(wtap *wth, wtap_rec *rec, int *err,
48
        char **err_info, int64_t *data_offset);
49
static bool systemd_journal_seek_read(wtap *wth, int64_t seek_off,
50
        wtap_rec *rec, int *err, char **err_info);
51
static bool systemd_journal_read_export_entry(FILE_T fh, wtap_rec *rec,
52
        int *err, char **err_info);
53
54
// The Journal Export Format specification doesn't place limits on entry
55
// lengths or lines per entry. We do.
56
0
#define MAX_EXPORT_ENTRY_LENGTH WTAP_MAX_PACKET_SIZE_STANDARD
57
0
#define MAX_EXPORT_ENTRY_LINES 100
58
59
// Strictly speaking, we only need __REALTIME_TIMESTAMP= since we use
60
// that to set the packet timestamp. According to
61
// https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
62
// __CURSOR= and __MONOTONIC_TIMESTAMP= should be present as well, so
63
// check for them order to improve our heuristics.
64
0
#define FLD__CURSOR "__CURSOR="
65
0
#define FLD__REALTIME_TIMESTAMP "__REALTIME_TIMESTAMP="
66
0
#define FLD__MONOTONIC_TIMESTAMP "__MONOTONIC_TIMESTAMP="
67
68
static int systemd_journal_file_type_subtype = -1;
69
70
void register_systemd_journal(void);
71
72
wtap_open_return_val systemd_journal_open(wtap *wth, int *err _U_, char **err_info _U_)
73
0
{
74
0
    char *entry_buff = (char*) g_malloc(MAX_EXPORT_ENTRY_LENGTH);
75
0
    char *entry_line = NULL;
76
0
    bool got_cursor = false;
77
0
    bool got_rt_ts = false;
78
0
    bool got_mt_ts = false;
79
0
    int line_num;
80
81
0
    errno = 0;
82
0
    for (line_num = 0; line_num < MAX_EXPORT_ENTRY_LINES; line_num++) {
83
0
        entry_line = file_gets(entry_buff, MAX_EXPORT_ENTRY_LENGTH, wth->fh);
84
0
        if (!entry_line) {
85
0
            break;
86
0
        }
87
0
        if (entry_line[0] == '\n') {
88
0
            break;
89
0
        } else if (strncmp(entry_line, FLD__CURSOR, strlen(FLD__CURSOR)) == 0) {
90
0
            got_cursor = true;
91
0
        } else if (strncmp(entry_line, FLD__REALTIME_TIMESTAMP, strlen(FLD__REALTIME_TIMESTAMP)) == 0) {
92
0
            got_rt_ts = true;
93
0
        } else if (strncmp(entry_line, FLD__MONOTONIC_TIMESTAMP, strlen(FLD__MONOTONIC_TIMESTAMP)) == 0) {
94
0
            got_mt_ts = true;
95
0
        }
96
0
    }
97
0
    g_free(entry_buff);
98
99
0
    if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) {
100
0
        return WTAP_OPEN_ERROR;
101
0
    }
102
103
0
    if (!got_cursor || !got_rt_ts || !got_mt_ts) {
104
0
        return WTAP_OPEN_NOT_MINE;
105
0
    }
106
107
0
    wth->file_type_subtype = systemd_journal_file_type_subtype;
108
0
    wth->subtype_read = systemd_journal_read;
109
0
    wth->subtype_seek_read = systemd_journal_seek_read;
110
0
    wth->file_encap = WTAP_ENCAP_SYSTEMD_JOURNAL;
111
0
    wth->file_tsprec = WTAP_TSPREC_USEC;
112
113
    /*
114
     * Add an IDB; we don't know how many interfaces were
115
     * involved, so we just say one interface, about which
116
     * we only know the link-layer type, snapshot length,
117
     * and time stamp resolution.
118
     */
119
0
    wtap_add_generated_idb(wth);
120
121
0
    return WTAP_OPEN_MINE;
122
0
}
123
124
/* Read the next packet */
125
static bool systemd_journal_read(wtap *wth, wtap_rec *rec, int *err,
126
        char **err_info, int64_t *data_offset)
127
0
{
128
0
    *data_offset = file_tell(wth->fh);
129
130
    /* Read record. */
131
0
    if (!systemd_journal_read_export_entry(wth->fh, rec, err, err_info)) {
132
        /* Read error or EOF */
133
0
        return false;
134
0
    }
135
136
0
    return true;
137
0
}
138
139
static bool systemd_journal_seek_read(wtap *wth, int64_t seek_off,
140
        wtap_rec *rec, int *err, char **err_info)
141
0
{
142
0
    if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
143
0
        return false;
144
145
    /* Read record. */
146
0
    if (!systemd_journal_read_export_entry(wth->random_fh, rec, err, err_info)) {
147
        /* Read error or EOF */
148
0
        if (*err == 0) {
149
            /* EOF means "short read" in random-access mode */
150
0
            *err = WTAP_ERR_SHORT_READ;
151
0
        }
152
0
        return false;
153
0
    }
154
0
    return true;
155
0
}
156
157
static bool systemd_journal_read_export_entry(FILE_T fh, wtap_rec *rec,
158
        int *err, char **err_info)
159
0
{
160
0
    char *entry_line = NULL;
161
0
    bool got_cursor = false;
162
0
    bool got_rt_ts = false;
163
0
    bool got_mt_ts = false;
164
0
    bool got_double_newline = false;
165
0
    int line_num;
166
0
    size_t rt_ts_len = strlen(FLD__REALTIME_TIMESTAMP);
167
168
0
    ws_buffer_assure_space(&rec->data, MAX_EXPORT_ENTRY_LENGTH);
169
170
0
    for (line_num = 0; line_num < MAX_EXPORT_ENTRY_LINES; line_num++) {
171
        // XXX - Should there be a file_gets_buffer() ?
172
0
        entry_line = file_gets((char*)ws_buffer_end_ptr(&rec->data), MAX_EXPORT_ENTRY_LENGTH - (int) ws_buffer_length(&rec->data), fh);
173
0
        if (!entry_line) {
174
0
            break;
175
0
        }
176
0
        ws_buffer_increase_length(&rec->data, strlen(entry_line));
177
0
        if (entry_line[0] == '\n') {
178
0
            got_double_newline = true;
179
0
            break;
180
0
        } else if (strncmp(entry_line, FLD__CURSOR, strlen(FLD__CURSOR)) == 0) {
181
0
            got_cursor = true;
182
0
        } else if (strncmp(entry_line, FLD__REALTIME_TIMESTAMP, rt_ts_len) == 0) {
183
0
            errno = 0;
184
0
            unsigned long rt_ts = strtoul(entry_line+rt_ts_len, NULL, 10);
185
0
            if (!errno) {
186
0
                rec->ts.secs = (time_t) rt_ts / 1000000;
187
0
                rec->ts.nsecs = (rt_ts % 1000000) * 1000;
188
0
                rec->tsprec = WTAP_TSPREC_USEC;
189
0
                got_rt_ts = true;
190
0
            }
191
0
        } else if (strncmp(entry_line, FLD__MONOTONIC_TIMESTAMP, strlen(FLD__MONOTONIC_TIMESTAMP)) == 0) {
192
0
            got_mt_ts = true;
193
0
        } else if (!strstr(entry_line, "=")) {
194
            // Start of binary data.
195
0
            if (ws_buffer_length(&rec->data) >= MAX_EXPORT_ENTRY_LENGTH - 8) {
196
0
                *err = WTAP_ERR_BAD_FILE;
197
0
                *err_info = ws_strdup_printf("systemd: binary length too long");
198
0
                return false;
199
0
            }
200
0
            uint64_t data_len;
201
0
            if (!wtap_read_bytes_buffer(fh, &rec->data, 8, err, err_info)) {
202
0
                return false;
203
0
            }
204
0
            data_len = pletohu64(&ws_buffer_end_ptr(&rec->data)[-8]);
205
0
            if (data_len < 1 || data_len - 1 >= MAX_EXPORT_ENTRY_LENGTH - ws_buffer_length(&rec->data)) {
206
0
                *err = WTAP_ERR_BAD_FILE;
207
0
                *err_info = ws_strdup_printf("systemd: binary data too long");
208
0
                return false;
209
0
            }
210
            // Data + trailing \n
211
0
            if (!wtap_read_bytes_buffer(fh, &rec->data, (unsigned) data_len + 1, err, err_info)) {
212
0
                return false;
213
0
            }
214
0
        }
215
0
        if (MAX_EXPORT_ENTRY_LENGTH < ws_buffer_length(&rec->data) + 2) { // \n\0
216
0
            break;
217
0
        }
218
0
    }
219
220
0
    if (!got_cursor || !got_rt_ts || !got_mt_ts) {
221
0
        return false;
222
0
    }
223
224
0
    if (!got_double_newline && !file_eof(fh)) {
225
0
        return false;
226
0
    }
227
228
0
    wtap_setup_systemd_journal_export_rec(rec);
229
0
    rec->block = wtap_block_create(WTAP_BLOCK_SYSTEMD_JOURNAL_EXPORT);
230
0
    rec->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;
231
0
    rec->rec_header.systemd_journal_export_header.record_len = (uint32_t) ws_buffer_length(&rec->data);
232
233
0
    return true;
234
0
}
235
236
static const struct supported_block_type systemd_journal_blocks_supported[] = {
237
    /*
238
     * We support systemd journal blocks, with no comments or other options.
239
     */
240
    { WTAP_BLOCK_SYSTEMD_JOURNAL_EXPORT, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
241
};
242
243
static const struct file_type_subtype_info systemd_journal_info = {
244
    "systemd journal export", "systemd_journal", NULL, NULL,
245
    false, BLOCKS_SUPPORTED(systemd_journal_blocks_supported),
246
    NULL, NULL, NULL
247
};
248
249
void register_systemd_journal(void)
250
14
{
251
14
  systemd_journal_file_type_subtype = wtap_register_file_type_subtype(&systemd_journal_info);
252
253
  /*
254
   * Register name for backwards compatibility with the
255
   * wtap_filetypes table in Lua.
256
   */
257
14
  wtap_register_backwards_compatibility_lua_name("SYSTEMD_JOURNAL",
258
14
                                                 systemd_journal_file_type_subtype);
259
14
}
260
261
/*
262
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
263
 *
264
 * Local variables:
265
 * c-basic-offset: 4
266
 * tab-width: 8
267
 * indent-tabs-mode: nil
268
 * End:
269
 *
270
 * vi: set shiftwidth=4 tabstop=8 expandtab:
271
 * :indentSize=4:tabSize=8:noTabs=true:
272
 */