Skip to content

Commit a93503b

Browse files
committed
syslog: size the log mapping so the caller can terminate it
The mapping was sized with int arithmetic on a long length, so a log larger than INT_MAX truncated before rounding and produced a mapping smaller than the read that follows it. The rounding also had no headroom guarantee: a length that is an exact page multiple left buffer[len] one byte past the end, and callers walking the buffer by line write a terminator there. kasld_syslog_alloc rounds strictly upward in long arithmetic and refuses a length that cannot be rounded without overflow, returning 0 so the caller reports the length and stops rather than mapping a wrong size.
1 parent a3d4d75 commit a93503b

1 file changed

Lines changed: 60 additions & 9 deletions

File tree

src/include/syslog.h

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "include/kasld/sysroot.h"
1212

1313
#include <errno.h>
14+
#include <limits.h>
1415
#include <stddef.h>
1516
#include <stdio.h>
1617
#include <sys/klog.h>
@@ -20,13 +21,35 @@
2021
#define SYSLOG_ACTION_READ_ALL 3
2122
#define SYSLOG_ACTION_SIZE_BUFFER 10
2223

24+
/* Size the mapping for a `len`-byte kernel log: `len` page-rounded STRICTLY
25+
* upward, so every byte of a len-sized read lands inside the mapping and
26+
* buffer[len] is still a writable byte the caller may terminate on. Callers
27+
* walking the buffer by line depend on that byte existing.
28+
*
29+
* Returns 0 when `len` is out of range, which both sources treat as "no log to
30+
* read". The bound is that the rounded size must still fit a positive int:
31+
* that is the width this interface reports a size in, and `int` is also what
32+
* klogctl() takes and returns. A log larger than that is refused rather than
33+
* partly read — a prefix of a kernel log would silently drop whatever leaks sat
34+
* in the rest of it.
35+
*
36+
* Both sources round through here so that neither can size a mapping from one
37+
* value and then fill it from another. */
38+
static size_t kasld_syslog_alloc(long len) {
39+
long page = getpagesize();
40+
41+
if (len <= 0 || page <= 0 || len > (long)INT_MAX - page)
42+
return 0;
43+
return (size_t)((len / page + 1) * page);
44+
}
45+
2346
/* Read /var/log/dmesg into an mmap'd buffer.
2447
* Fallback when klogctl() is denied (dmesg_restrict=1).
2548
*/
2649
static int read_dmesg_log_file(char **buffer, int *size) {
2750
FILE *f;
2851
long len;
29-
int alloc;
52+
size_t alloc;
3053
const char *path = "/var/log/dmesg";
3154

3255
f = kasld_fopen(path, "rb");
@@ -42,15 +65,23 @@ static int read_dmesg_log_file(char **buffer, int *size) {
4265

4366
rewind(f);
4467

45-
alloc = ((int)len / getpagesize() + 1) * getpagesize();
68+
/* The mapping and the read are sized from the same `len`. Under
69+
* KASLD_SYSROOT this file comes from a captured tree rather than from the
70+
* running kernel, so its length is whatever that tree says it is. */
71+
alloc = kasld_syslog_alloc(len);
72+
if (alloc == 0) {
73+
fprintf(stderr, "[-] %s: implausible length (%ld bytes)\n", path, len);
74+
fclose(f);
75+
return 1;
76+
}
4677
*buffer = (char *)mmap(NULL, alloc, PROT_READ | PROT_WRITE,
4778
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
4879
if (*buffer == MAP_FAILED) {
4980
fclose(f);
5081
return 1;
5182
}
5283

53-
*size = (int)fread(*buffer, 1, len, f);
84+
*size = (int)fread(*buffer, 1, (size_t)len, f);
5485
fclose(f);
5586

5687
if (*size <= 0) {
@@ -64,14 +95,20 @@ static int read_dmesg_log_file(char **buffer, int *size) {
6495
/* mmap entire kernel message ring buffer into +buffer+.
6596
* Falls back to /var/log/dmesg when klogctl() is denied.
6697
*
98+
* On success *size is the number of bytes read and the mapping is strictly
99+
* larger, so buffer[*size] is a writable byte inside it: a caller may walk the
100+
* buffer by line and terminate the last one in place. Both sources establish
101+
* that through kasld_syslog_alloc().
102+
*
67103
* Copied from exploit code by xairy:
68104
* https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c
69105
*
70106
* Defined static: each component is compiled as a standalone binary,
71107
* so header-embedded implementations are the intended pattern.
72108
*/
73109
static int mmap_syslog(char **buffer, int *size) {
74-
int alloc;
110+
size_t alloc;
111+
int ring;
75112

76113
/* Offline analysis: under KASLD_SYSROOT, klogctl() would read the live HOST
77114
* kernel log, not the analysed tree, so read the captured /var/log/dmesg
@@ -81,26 +118,40 @@ static int mmap_syslog(char **buffer, int *size) {
81118
if (kasld_sysroot())
82119
return read_dmesg_log_file(buffer, size);
83120

84-
*size = klogctl(SYSLOG_ACTION_SIZE_BUFFER, 0, 0);
85-
if (*size == -1) {
121+
/* The reported ring size is held apart from *size until the read succeeds,
122+
* so a fallback to the log file never inherits a size from the ring. */
123+
ring = klogctl(SYSLOG_ACTION_SIZE_BUFFER, 0, 0);
124+
if (ring < 0) {
86125
perror("[-] klogctl(SYSLOG_ACTION_SIZE_BUFFER)");
87126
return read_dmesg_log_file(buffer, size);
88127
}
89128

90-
alloc = (*size / getpagesize() + 1) * getpagesize();
129+
/* A ring can be raised to 2 GiB by the log_buf_len boot parameter, which is
130+
* past what the rounded size can report as a positive int. */
131+
alloc = kasld_syslog_alloc(ring);
132+
if (alloc == 0)
133+
return read_dmesg_log_file(buffer, size);
134+
91135
*buffer = (char *)mmap(NULL, alloc, PROT_READ | PROT_WRITE,
92136
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
93137
if (*buffer == MAP_FAILED) {
94138
return read_dmesg_log_file(buffer, size);
95139
}
96140

97-
*size = klogctl(SYSLOG_ACTION_READ_ALL, *buffer, alloc);
98-
if (*size == -1) {
141+
*size = klogctl(SYSLOG_ACTION_READ_ALL, *buffer, (int)alloc);
142+
if (*size < 0) {
99143
perror("[-] klogctl(SYSLOG_ACTION_READ_ALL)");
100144
munmap(*buffer, alloc);
101145
return read_dmesg_log_file(buffer, size);
102146
}
103147

148+
/* alloc is strictly above the size the kernel reported, so a full read still
149+
* leaves buffer[*size] inside the mapping. Clamped rather than assumed: the
150+
* terminable-byte invariant must not rest on the two klogctl calls agreeing
151+
* about how much the ring holds. */
152+
if ((size_t)*size >= alloc)
153+
*size = (int)alloc - 1;
154+
104155
return 0;
105156
}
106157

0 commit comments

Comments
 (0)