1 /* logcat.c
2  *
3  * Copyright 2014, Michal Labedzki for Tieto Corporation
4  *
5  * SPDX-License-Identifier: GPL-2.0-or-later
6  */
7 
8 #include "config.h"
9 
10 #include <string.h>
11 
12 #include "wtap-int.h"
13 #include "file_wrappers.h"
14 
15 #include "logcat.h"
16 
17 static int logcat_file_type_subtype = -1;
18 
19 void register_logcat(void);
20 
21 /* Returns '?' for invalid priorities */
get_priority(const guint8 priority)22 static gchar get_priority(const guint8 priority) {
23     static gchar priorities[] = "??VDIWEFS";
24 
25     if (priority >= (guint8) sizeof(priorities))
26         return '?';
27 
28     return priorities[priority];
29 }
30 
31 /*
32  * Returns:
33  *
34  *  -2 if we get an EOF at the beginning;
35  *  -1 on an I/O error;
36  *  0 if the record doesn't appear to be valid;
37  *  1-{max gint} as a version number if we got a valid record.
38  */
detect_version(FILE_T fh,int * err,gchar ** err_info)39 static gint detect_version(FILE_T fh, int *err, gchar **err_info)
40 {
41     guint16                  payload_length;
42     guint16                  hdr_size;
43     guint16                  read_sofar;
44     guint16                  entry_len;
45     gint                     version;
46     struct logger_entry     *log_entry;
47     struct logger_entry_v2  *log_entry_v2;
48     guint8                  *buffer;
49     guint16                  tmp;
50     guint8                  *msg_payload;
51     guint8                  *msg_part;
52     guint8                  *msg_end;
53     guint16                  msg_len;
54 
55     /* 16-bit payload length */
56     if (!wtap_read_bytes_or_eof(fh, &tmp, 2, err, err_info)) {
57         if (*err == 0) {
58             /*
59              * Got an EOF at the beginning.
60              */
61             return -2;
62         }
63         if (*err != WTAP_ERR_SHORT_READ)
64             return -1;
65         return 0;
66     }
67     payload_length = pletoh16(&tmp);
68 
69     /* must contain at least priority and two nulls as separator */
70     if (payload_length < 3)
71         return 0;
72     /* payload length may not exceed the maximum payload size */
73     if (payload_length > LOGGER_ENTRY_MAX_PAYLOAD)
74         return 0;
75 
76     /* 16-bit header length (or padding, equal to 0x0000) */
77     if (!wtap_read_bytes(fh, &tmp, 2, err, err_info)) {
78         if (*err != WTAP_ERR_SHORT_READ)
79             return -1;
80         return 0;
81     }
82     hdr_size = pletoh16(&tmp);
83     read_sofar = 4;
84 
85     /* ensure buffer is large enough for all versions */
86     buffer = (guint8 *) g_malloc(sizeof(*log_entry_v2) + payload_length);
87     log_entry_v2 = (struct logger_entry_v2 *)(void *) buffer;
88     log_entry = (struct logger_entry *)(void *) buffer;
89 
90     /* cannot rely on __pad being 0 for v1, use heuristics to find out what
91      * version is in use. First assume the smallest msg. */
92     for (version = 1; version <= 2; ++version) {
93         if (version == 1) {
94             msg_payload = (guint8 *) (log_entry + 1);
95             entry_len = sizeof(*log_entry) + payload_length;
96         } else if (version == 2) {
97             /* v2 is 4 bytes longer */
98             msg_payload = (guint8 *) (log_entry_v2 + 1);
99             entry_len = sizeof(*log_entry_v2) + payload_length;
100             if (hdr_size != sizeof(*log_entry_v2))
101                 continue;
102         } else {
103             continue;
104         }
105 
106         if (!wtap_read_bytes(fh, buffer + read_sofar, entry_len - read_sofar, err, err_info)) {
107             g_free(buffer);
108             if (*err != WTAP_ERR_SHORT_READ)
109                 return -1;
110             return 0;
111         }
112         read_sofar += entry_len - read_sofar;
113 
114         /* A v2 msg has a 32-bit userid instead of v1 priority */
115         if (get_priority(msg_payload[0]) == '?')
116             continue;
117 
118         /* Is there a terminating '\0' for the tag? */
119         msg_part = (guint8 *) memchr(msg_payload, '\0', payload_length - 1);
120         if (msg_part == NULL)
121             continue;
122 
123         /* if msg is '\0'-terminated, is it equal to the payload len? */
124         ++msg_part;
125         msg_len = (guint16)(payload_length - (msg_part - msg_payload));
126         msg_end = (guint8 *) memchr(msg_part, '\0', msg_len);
127         /* is the end of the buffer (-1) equal to the end of msg? */
128         if (msg_end && (msg_payload + payload_length - 1 != msg_end))
129             continue;
130 
131         g_free(buffer);
132         return version;
133     }
134 
135     /* No version number is valid */
136     g_free(buffer);
137     return 0;
138 }
139 
logcat_exported_pdu_length(const guint8 * pd)140 gint logcat_exported_pdu_length(const guint8 *pd) {
141     const guint16  *tag;
142     const guint16  *tag_length;
143     gint            length = 0;
144 
145     tag = (const guint16 *)(const void *) pd;
146 
147     while(GINT16_FROM_BE(*tag)) {
148         tag_length = (const guint16 *)(const void *) (pd + 2);
149         length += 2 + 2 + GINT16_FROM_BE(*tag_length);
150 
151         pd += 2 + 2 + GINT16_FROM_BE(*tag_length);
152         tag = (const guint16 *)(const void *) pd;
153     }
154 
155     length += 2 + 2;
156 
157     return length;
158 }
159 
logcat_read_packet(struct logcat_phdr * logcat,FILE_T fh,wtap_rec * rec,Buffer * buf,int * err,gchar ** err_info)160 static gboolean logcat_read_packet(struct logcat_phdr *logcat, FILE_T fh,
161     wtap_rec *rec, Buffer *buf, int *err, gchar **err_info)
162 {
163     gint                 packet_size;
164     guint16              payload_length;
165     guint                tmp[2];
166     guint8              *pd;
167     struct logger_entry *log_entry;
168 
169     if (!wtap_read_bytes_or_eof(fh, &tmp, 2, err, err_info)) {
170         return FALSE;
171     }
172     payload_length = pletoh16(tmp);
173 
174     if (logcat->version == 1) {
175         packet_size = (gint)sizeof(struct logger_entry) + payload_length;
176     } else if (logcat->version == 2) {
177         packet_size = (gint)sizeof(struct logger_entry_v2) + payload_length;
178     } else {
179         return FALSE;
180     }
181     /*
182      * The maximum value of payload_length is 65535, which, even after
183      * the size of the logger entry structure is added to it, is less
184      * than WTAP_MAX_PACKET_SIZE_STANDARD will ever be, so we don't need to check
185      * it.
186      */
187 
188     ws_buffer_assure_space(buf, packet_size);
189     pd = ws_buffer_start_ptr(buf);
190     log_entry = (struct logger_entry *)(void *) pd;
191 
192     /* Copy the first two bytes of the packet. */
193     memcpy(pd, tmp, 2);
194 
195     /* Read the rest of the packet. */
196     if (!wtap_read_bytes(fh, pd + 2, packet_size - 2, err, err_info)) {
197         return FALSE;
198     }
199 
200     rec->rec_type = REC_TYPE_PACKET;
201     rec->block = wtap_block_create(WTAP_BLOCK_PACKET);
202     rec->presence_flags = WTAP_HAS_TS;
203     rec->ts.secs = (time_t) GINT32_FROM_LE(log_entry->sec);
204     rec->ts.nsecs = GINT32_FROM_LE(log_entry->nsec);
205     rec->rec_header.packet_header.caplen = packet_size;
206     rec->rec_header.packet_header.len = packet_size;
207 
208     rec->rec_header.packet_header.pseudo_header.logcat.version = logcat->version;
209 
210     return TRUE;
211 }
212 
logcat_read(wtap * wth,wtap_rec * rec,Buffer * buf,int * err,gchar ** err_info,gint64 * data_offset)213 static gboolean logcat_read(wtap *wth, wtap_rec *rec, Buffer *buf,
214     int *err, gchar **err_info, gint64 *data_offset)
215 {
216     *data_offset = file_tell(wth->fh);
217 
218     return logcat_read_packet((struct logcat_phdr *) wth->priv, wth->fh,
219         rec, buf, err, err_info);
220 }
221 
logcat_seek_read(wtap * wth,gint64 seek_off,wtap_rec * rec,Buffer * buf,int * err,gchar ** err_info)222 static gboolean logcat_seek_read(wtap *wth, gint64 seek_off,
223     wtap_rec *rec, Buffer *buf,
224     int *err, gchar **err_info)
225 {
226     if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
227         return FALSE;
228 
229     if (!logcat_read_packet((struct logcat_phdr *) wth->priv, wth->random_fh,
230          rec, buf, err, err_info)) {
231         if (*err == 0)
232             *err = WTAP_ERR_SHORT_READ;
233         return FALSE;
234     }
235     return TRUE;
236 }
237 
logcat_open(wtap * wth,int * err,gchar ** err_info)238 wtap_open_return_val logcat_open(wtap *wth, int *err, gchar **err_info)
239 {
240     gint                version;
241     gint                tmp_version;
242     struct logcat_phdr *logcat;
243 
244     /* check first 3 packets (or 2 or 1 if EOF) versions to check file format is correct */
245     version = detect_version(wth->fh, err, err_info); /* first packet */
246     if (version == -1)
247         return WTAP_OPEN_ERROR; /* I/O error */
248     if (version == 0)
249         return WTAP_OPEN_NOT_MINE;  /* not a logcat file */
250     if (version == -2)
251         return WTAP_OPEN_NOT_MINE;  /* empty file, so not any type of file */
252 
253     tmp_version = detect_version(wth->fh, err, err_info); /* second packet */
254     if (tmp_version == -1)
255         return WTAP_OPEN_ERROR; /* I/O error */
256     if (tmp_version == 0)
257         return WTAP_OPEN_NOT_MINE;  /* not a logcat file */
258     if (tmp_version != -2) {
259         /* we've read two packets; do they have the same version? */
260         if (tmp_version != version) {
261             /* no, so this is presumably not a logcat file */
262             return WTAP_OPEN_NOT_MINE;
263         }
264 
265         tmp_version = detect_version(wth->fh, err, err_info); /* third packet */
266         if (tmp_version < 0)
267             return WTAP_OPEN_ERROR; /* I/O error */
268         if (tmp_version == 0)
269             return WTAP_OPEN_NOT_MINE;  /* not a logcat file */
270         if (tmp_version != -2) {
271             /*
272              * we've read three packets and the first two have the same
273              * version; does the third have the same version?
274              */
275             if (tmp_version != version) {
276                 /* no, so this is presumably not a logcat file */
277                 return WTAP_OPEN_NOT_MINE;
278             }
279         }
280     }
281 
282     if (file_seek(wth->fh, 0, SEEK_SET, err) == -1)
283         return WTAP_OPEN_ERROR;
284 
285     logcat = g_new(struct logcat_phdr, 1);
286     logcat->version = version;
287 
288     wth->priv = logcat;
289 
290     wth->file_type_subtype = logcat_file_type_subtype;
291     wth->file_encap = WTAP_ENCAP_LOGCAT;
292     wth->snapshot_length = 0;
293 
294     wth->subtype_read = logcat_read;
295     wth->subtype_seek_read = logcat_seek_read;
296     wth->file_tsprec = WTAP_TSPREC_USEC;
297 
298     /*
299      * Add an IDB; we don't know how many interfaces were
300      * involved, so we just say one interface, about which
301      * we only know the link-layer type, snapshot length,
302      * and time stamp resolution.
303      */
304     wtap_add_generated_idb(wth);
305 
306     return WTAP_OPEN_MINE;
307 }
308 
logcat_dump_can_write_encap(int encap)309 static int logcat_dump_can_write_encap(int encap)
310 {
311     if (encap == WTAP_ENCAP_PER_PACKET)
312         return WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED;
313 
314     if (encap != WTAP_ENCAP_LOGCAT && encap != WTAP_ENCAP_WIRESHARK_UPPER_PDU)
315         return WTAP_ERR_UNWRITABLE_ENCAP;
316 
317     return 0;
318 }
319 
logcat_binary_dump(wtap_dumper * wdh,const wtap_rec * rec,const guint8 * pd,int * err,gchar ** err_info _U_)320 static gboolean logcat_binary_dump(wtap_dumper *wdh,
321     const wtap_rec *rec,
322     const guint8 *pd, int *err, gchar **err_info _U_)
323 {
324     int caplen;
325 
326     /* We can only write packet records. */
327     if (rec->rec_type != REC_TYPE_PACKET) {
328         *err = WTAP_ERR_UNWRITABLE_REC_TYPE;
329         return FALSE;
330     }
331 
332     /*
333      * Make sure this packet doesn't have a link-layer type that
334      * differs from the one for the file.
335      */
336     if (wdh->encap != rec->rec_header.packet_header.pkt_encap) {
337         *err = WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED;
338         return FALSE;
339     }
340 
341     caplen = rec->rec_header.packet_header.caplen;
342 
343     /* Skip EXPORTED_PDU*/
344     if (wdh->encap == WTAP_ENCAP_WIRESHARK_UPPER_PDU) {
345         gint skipped_length;
346 
347         skipped_length = logcat_exported_pdu_length(pd);
348         pd += skipped_length;
349         caplen -= skipped_length;
350     }
351 
352     if (!wtap_dump_file_write(wdh, pd, caplen, err))
353         return FALSE;
354 
355     wdh->bytes_dumped += caplen;
356 
357     return TRUE;
358 }
359 
logcat_binary_dump_open(wtap_dumper * wdh,int * err _U_,gchar ** err_info _U_)360 static gboolean logcat_binary_dump_open(wtap_dumper *wdh, int *err _U_,
361     gchar **err_info _U_)
362 {
363     wdh->subtype_write = logcat_binary_dump;
364 
365     return TRUE;
366 }
367 
368 static const struct supported_block_type logcat_blocks_supported[] = {
369     /*
370      * We support packet blocks, with no comments or other options.
371      */
372     { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
373 };
374 
375 static const struct file_type_subtype_info logcat_info = {
376     "Android Logcat Binary format", "logcat", "logcat", NULL,
377     FALSE, BLOCKS_SUPPORTED(logcat_blocks_supported),
378     logcat_dump_can_write_encap, logcat_binary_dump_open, NULL
379 };
380 
register_logcat(void)381 void register_logcat(void)
382 {
383     logcat_file_type_subtype = wtap_register_file_type_subtype(&logcat_info);
384 
385     /*
386      * Register name for backwards compatibility with the
387      * wtap_filetypes table in Lua.
388      */
389     wtap_register_backwards_compatibility_lua_name("LOGCAT",
390                                                    logcat_file_type_subtype);
391 }
392 
393 /*
394  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
395  *
396  * Local variables:
397  * c-basic-offset: 4
398  * tab-width: 8
399  * indent-tabs-mode: nil
400  * End:
401  *
402  * vi: set shiftwidth=4 tabstop=8 expandtab:
403  * :indentSize=4:tabSize=8:noTabs=true:
404  */
405