1 /*
2 * Copyright (C) 2011 Red Hat, Inc.
3 *
4 * All rights reserved.
5 *
6 * Author: Angus Salkeld <asalkeld@redhat.com>
7 *
8 * libqb is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation, either version 2.1 of the License, or
11 * (at your option) any later version.
12 *
13 * libqb is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with libqb. If not, see <http://www.gnu.org/licenses/>.
20 */
21 #include "os_base.h"
22
23 #include <qb/qbrb.h>
24 #include "util_int.h"
25 #include "log_int.h"
26 #include "ringbuffer_int.h"
27
28 #define BB_MIN_ENTRY_SIZE (4 * sizeof(uint32_t) +\
29 sizeof(uint8_t) +\
30 2 * sizeof(char) + sizeof(time_t))
31
32
33 static void
34 _blackbox_reload(int32_t target)
35 {
36 struct qb_log_target *t = qb_log_target_get(target);
37
38 if (t->instance == NULL) {
39 return;
40 }
41 qb_rb_close(t->instance);
42 t->instance = qb_rb_open(t->filename, t->size,
43 QB_RB_FLAG_CREATE | QB_RB_FLAG_OVERWRITE, 0);
44 }
45
46 /* <u32> file lineno
47 * <u32> tags
48 * <u8> priority
49 * <u32> function name length
50 * <string> function name
51 * <u32> buffer length
52 * <string> buffer
53 */
54 static void
55 _blackbox_vlogger(int32_t target,
56 struct qb_log_callsite *cs, struct timespec *timestamp, va_list ap)
57 {
58 size_t max_size;
59 size_t actual_size;
60 uint32_t fn_size;
61 char *chunk;
62 char *msg_len_pt;
63 uint32_t msg_len;
64 struct qb_log_target *t = qb_log_target_get(target);
65
66 if (t->instance == NULL) {
67 return;
68 }
69
70 fn_size = strlen(cs->function) + 1;
71
72 actual_size = 4 * sizeof(uint32_t) + sizeof(uint8_t) + fn_size + sizeof(struct timespec);
73 max_size = actual_size + t->max_line_length;
74
75 chunk = qb_rb_chunk_alloc(t->instance, max_size);
76
77 if (chunk == NULL) {
78 /* something bad has happened. abort blackbox logging */
79 qb_util_perror(LOG_ERR, "Blackbox allocation error, aborting blackbox log %s", t->filename);
80 qb_rb_close(qb_rb_lastref_and_ret(
81 (struct qb_ringbuffer_s **) &t->instance
82 ));
83 return;
84 }
85
86 /* line number */
87 memcpy(chunk, &cs->lineno, sizeof(uint32_t));
88 chunk += sizeof(uint32_t);
89
90 /* tags */
91 memcpy(chunk, &cs->tags, sizeof(uint32_t));
92 chunk += sizeof(uint32_t);
93
94 /* log level/priority */
95 memcpy(chunk, &cs->priority, sizeof(uint8_t));
96 chunk += sizeof(uint8_t);
97
98 /* function name */
99 memcpy(chunk, &fn_size, sizeof(uint32_t));
100 chunk += sizeof(uint32_t);
101 memcpy(chunk, cs->function, fn_size);
102 chunk += fn_size;
103
104 /* timestamp */
105 memcpy(chunk, timestamp, sizeof(struct timespec));
106 chunk += sizeof(struct timespec);
107
108 /* log message length */
109 msg_len_pt = chunk;
110 chunk += sizeof(uint32_t);
111
112 /* log message */
113 msg_len = qb_vsnprintf_serialize(chunk, t->max_line_length, cs->format, ap);
114 if (msg_len >= t->max_line_length) {
115 chunk = msg_len_pt + sizeof(uint32_t); /* Reset */
116
117 /* Leave this at QB_LOG_MAX_LEN so as not to overflow the blackbox */
118 msg_len = qb_vsnprintf_serialize(chunk, QB_LOG_MAX_LEN,
119 "Log message too long to be stored in the blackbox. "\
120 "Maximum is QB_LOG_MAX_LEN" , ap);
121 }
122
123 actual_size += msg_len;
124
125 /* now that we know the length, write it
126 */
127 memcpy(msg_len_pt, &msg_len, sizeof(uint32_t));
128
129 (void)qb_rb_chunk_commit(t->instance, actual_size);
130 }
131
132 static void
133 _blackbox_close(int32_t target)
134 {
135 struct qb_log_target *t = qb_log_target_get(target);
136
137 qb_rb_close(qb_rb_lastref_and_ret(
138 (struct qb_ringbuffer_s **) &t->instance
139 ));
140 }
141
142 int32_t
143 qb_log_blackbox_open(struct qb_log_target *t)
144 {
145 if (t->size < 1024) {
146 return -EINVAL;
147 }
148 snprintf(t->filename, PATH_MAX, "%s-%d-blackbox", t->name, getpid());
149
150 t->instance = qb_rb_open(t->filename, t->size,
151 QB_RB_FLAG_CREATE | QB_RB_FLAG_OVERWRITE, 0);
152 if (t->instance == NULL) {
153 return -errno;
154 }
155
156 t->logger = NULL;
157 t->vlogger = _blackbox_vlogger;
158 t->reload = _blackbox_reload;
159 t->close = _blackbox_close;
160 return 0;
161 }
162
163 /*
164 * This is designed to look as much like the ringbuffer header
165 * as possible so that we can distinguish an old RB dump
166 * from a new one with this header.
167 */
168
169 struct _blackbox_file_header {
170 uint32_t word_size;
171 uint32_t read_pt;
172 uint32_t write_pt;
173 uint32_t version;
174 uint32_t hash;
175 } __attribute__((packed));
176
177 /* Values we expect for a 'new' header */
178 #define QB_BLACKBOX_HEADER_WORDSIZE 0
179 #define QB_BLACKBOX_HEADER_READPT 0xCCBBCCBB
180 #define QB_BLACKBOX_HEADER_WRITEPT 0xBBCCBBCC
181 #define QB_BLACKBOX_HEADER_VERSION 2
182 #define QB_BLACKBOX_HEADER_HASH 0
183
184 ssize_t
185 qb_log_blackbox_write_to_file(const char *filename)
186 {
187 ssize_t written_size = 0;
188 struct qb_log_target *t;
189 struct _blackbox_file_header header;
190 int fd = open(filename, O_CREAT | O_RDWR, 0700);
191
192 if (fd < 0) {
193 return -errno;
194 }
195
196 /* Write header, so we know this is a 'new' format blackbox */
197 header.word_size = QB_BLACKBOX_HEADER_WORDSIZE;
198 header.read_pt = QB_BLACKBOX_HEADER_READPT;
199 header.write_pt = QB_BLACKBOX_HEADER_WRITEPT;
200 header.version = QB_BLACKBOX_HEADER_VERSION;
201 header.hash = QB_BLACKBOX_HEADER_HASH;
202 written_size = write(fd, &header, sizeof(header));
203 if (written_size < sizeof(header)) {
204 close(fd);
205 return written_size;
206 }
207
208 t = qb_log_target_get(QB_LOG_BLACKBOX);
209 if (t->instance) {
210 written_size += qb_rb_write_to_file(t->instance, fd);
211 } else {
212 written_size = -ENOENT;
213 }
214 close(fd);
215
216 return written_size;
217 }
218
219 int
220 qb_log_blackbox_print_from_file(const char *bb_filename)
221 {
222 qb_ringbuffer_t *instance;
223 ssize_t bytes_read;
224 int max_size = 2 * QB_LOG_MAX_LEN;
225 char *chunk;
226 int fd;
227 int err = 0;
228 int saved_errno;
229 struct _blackbox_file_header header;
230 int have_timespecs = 0;
231 char time_buf[64];
232
233 fd = open(bb_filename, 0);
|
(1) Event cond_false: |
Condition "fd < 0", taking false branch. |
234 if (fd < 0) {
235 saved_errno = errno;
236 qb_util_perror(LOG_ERR, "qb_log_blackbox_print_from_file");
237 return -saved_errno;
|
(2) Event if_end: |
End of if statement. |
238 }
239
240 /* Read the header. If it looks like one of ours then
241 we know we have hi-res timestamps */
242 err = read(fd, &header, sizeof(header));
|
(3) Event cond_false: |
Condition "err < 20UL /* sizeof (header) */", taking false branch. |
243 if (err < sizeof(header)) {
244 saved_errno = errno;
245 close(fd);
246 return -saved_errno;
|
(4) Event if_end: |
End of if statement. |
247 }
248
|
(5) Event cond_true: |
Condition "header.word_size == 0", taking true branch. |
|
(6) Event cond_true: |
Condition "header.read_pt == 3434859707U", taking true branch. |
|
(7) Event cond_true: |
Condition "header.write_pt == 3150756812U", taking true branch. |
|
(8) Event cond_true: |
Condition "header.version == 2", taking true branch. |
|
(9) Event cond_true: |
Condition "header.hash == 0", taking true branch. |
249 if (header.word_size == QB_BLACKBOX_HEADER_WORDSIZE &&
250 header.read_pt == QB_BLACKBOX_HEADER_READPT &&
251 header.write_pt == QB_BLACKBOX_HEADER_WRITEPT &&
252 header.version == QB_BLACKBOX_HEADER_VERSION &&
253 header.hash == QB_BLACKBOX_HEADER_HASH) {
254 have_timespecs = 1;
|
(10) Event if_fallthrough: |
Falling through to end of if statement. |
255 } else {
256 (void)lseek(fd, 0, SEEK_SET);
|
(11) Event if_end: |
End of if statement. |
257 }
258
259
|
(12) Event tainted_return_value: |
Function "qb_rb_create_from_file" returns tainted data. [details] |
|
(13) Event var_assign: |
Assigning: "instance" = "qb_rb_create_from_file(fd, 0U)", which taints "instance". |
| Also see events: |
[tainted_data][remediation] |
260 instance = qb_rb_create_from_file(fd, 0);
261 close(fd);
|
(14) Event cond_false: |
Condition "instance == NULL", taking false branch. |
262 if (instance == NULL) {
263 return -EIO;
|
(15) Event if_end: |
End of if statement. |
264 }
265 chunk = malloc(max_size);
|
(16) Event cond_false: |
Condition "!chunk", taking false branch. |
266 if (!chunk) {
267 goto cleanup;
|
(17) Event if_end: |
End of if statement. |
268 }
269
270 do {
271 char *ptr;
272 uint32_t lineno;
273 uint32_t tags;
274 uint8_t priority;
275 uint32_t fn_size;
276 char *function;
277 uint32_t len;
278 struct timespec timestamp;
279 time_t time_sec;
280 uint32_t msg_len;
281 struct tm *tm;
282 char message[QB_LOG_MAX_LEN];
283
|
(18) Event tainted_data: |
Passing tainted expression "instance->shared_hdr" to "qb_rb_chunk_read", which uses it as a divisor or modulus. [details] |
|
(19) Event remediation: |
Ensure that tainted values are properly sanitized, by checking that their values are within a permissible range. |
| Also see events: |
[tainted_return_value][var_assign] |
284 bytes_read = qb_rb_chunk_read(instance, chunk, max_size, 0);
285
286 if (bytes_read >= 0 && bytes_read < BB_MIN_ENTRY_SIZE) {
287 printf("ERROR Corrupt file: blackbox header too small.\n");
288 err = -1;
289 goto cleanup;
290 } else if (bytes_read < 0) {
291 errno = -bytes_read;
292 perror("ERROR: qb_rb_chunk_read failed");
293 err = -EIO;
294 goto cleanup;
295 }
296 ptr = chunk;
297
298 /* lineno */
299 memcpy(&lineno, ptr, sizeof(uint32_t));
300 ptr += sizeof(uint32_t);
301
302 /* tags */
303 memcpy(&tags, ptr, sizeof(uint32_t));
304 ptr += sizeof(uint32_t);
305
306 /* priority */
307 memcpy(&priority, ptr, sizeof(uint8_t));
308 ptr += sizeof(uint8_t);
309
310 /* function size & name */
311 memcpy(&fn_size, ptr, sizeof(uint32_t));
312 if ((fn_size + BB_MIN_ENTRY_SIZE) > bytes_read) {
313 #ifndef S_SPLINT_S
314 printf("ERROR Corrupt file: fn_size way too big %" PRIu32 "\n", fn_size);
315 err = -EIO;
316 #endif /* S_SPLINT_S */
317 goto cleanup;
318 }
319 if (fn_size <= 0) {
320 #ifndef S_SPLINT_S
321 printf("ERROR Corrupt file: fn_size negative %" PRIu32 "\n", fn_size);
322 err = -EIO;
323 #endif /* S_SPLINT_S */
324 goto cleanup;
325 }
326 ptr += sizeof(uint32_t);
327
328 function = ptr;
329 ptr += fn_size;
330
331 /* timestamp size & content */
332 if (have_timespecs) {
333 memcpy(×tamp, ptr, sizeof(struct timespec));
334 ptr += sizeof(struct timespec);
335 time_sec = timestamp.tv_sec;
336 } else {
337 memcpy(&time_sec, ptr, sizeof(time_t));
338 ptr += sizeof(time_t);
339 timestamp.tv_nsec = 0LL;
340 }
341
342 tm = localtime(&time_sec);
343 if (tm) {
344 int slen = strftime(time_buf,
345 sizeof(time_buf), "%b %d %T",
346 tm);
347 snprintf(time_buf+slen, sizeof(time_buf) - slen, ".%03llu", timestamp.tv_nsec/QB_TIME_NS_IN_MSEC);
348 } else {
349 snprintf(time_buf, sizeof(time_buf), "%ld",
350 (long int)time_sec);
351 }
352 /* message length */
353 memcpy(&msg_len, ptr, sizeof(uint32_t));
354 if (msg_len > QB_LOG_MAX_LEN || msg_len <= 0) {
355 #ifndef S_SPLINT_S
356 printf("ERROR Corrupt file: msg_len out of bounds %" PRIu32 "\n", msg_len);
357 err = -EIO;
358 #endif /* S_SPLINT_S */
359 goto cleanup;
360 }
361
362 ptr += sizeof(uint32_t);
363
364 /* message content */
365 len = qb_vsnprintf_deserialize(message, QB_LOG_MAX_LEN, ptr);
366 assert(len > 0);
367 message[len] = '\0';
368 len--;
369 while (len > 0 && (message[len] == '\n' || message[len] == '\0')) {
370 message[len] = '\0';
371 len--;
372 }
373
374 printf("%-7s %s %s(%u):%u: %s\n",
375 qb_log_priority2str(priority),
376 time_buf, function, lineno, tags, message);
377
378 } while (bytes_read > BB_MIN_ENTRY_SIZE);
379
380 cleanup:
381 qb_rb_close(instance);
382 free(chunk);
383 return err;
384 }
385