Here's yet another example of dealing with a potential
problem at the symptom level, instead of addressing it
at the source. So, we will reverse that original patch
referenced below in preparation for a proper solution.
[ at the least, this ugly code should have used that ]
[ existing MAXTBL macro, making it a little prettier ]
Reference(s):
. original qualys patch
0102-top-Check-sortindx.patch
commit d5b8ac7139
Signed-off-by: Jim Warner <james.warner@comcast.net>
This patch replaces an original patch referenced below
which has now been reversed. We now validate variables
'graph_cpus', 'graph_mems' and 'summ_mscale' just once
at startup. Thereafter, top enforces the proper range.
[ we afford the same treatment to that 'task_mscale' ]
[ variable, which was ignored in the original patch. ]
Reference(s):
. original qualys patch
0099-top-Check-graph_cpus-graph_mems-and-summ_mscale.patch
commit cd8ba5670e
Signed-off-by: Jim Warner <james.warner@comcast.net>
The variables graph_cpus, graph_mems & summ_mscale are
all well managed in a running top. They were, however,
each vulnerable to tampering via the rcfile. So rather
than continually addressing the symptoms, we'll attack
the root cause just once at startup in the next patch.
Reference(s):
. original qualys patch
0099-top-Check-graph_cpus-graph_mems-and-summ_mscale.patch
commit cd8ba5670e
Signed-off-by: Jim Warner <james.warner@comcast.net>
While it's only documented (so far) in commit text and
an occasional email I've tried to maintain some coding
standards primarily for reference/navigation purposes.
They also served, I felt, as useful mental challenges.
Someday I will get around to formerly documenting them
but in the meantime here are the ones for this commit:
. functions are grouped into logical (i hope) sections
. functions & sections are ordered to avoid prototypes
. function names are alphabetical within every section
. all functions & sections must be referenced in top.h
This patch just attempts to honor the above standards,
while also covering this new behavior in the man page.
[ please note that the net result of these 2 patches ]
[ is simply to avoid pathname truncations should our ]
[ limit of 1024 be exceeded. they do not have a role ]
[ in solving the 'local privilege escalation' issue. ]
[ and we can never prevent a user from setting their ]
[ HOME var to a directory writable by some attacker! ]
[ the only real protection for that CVE-2018-1122 is ]
[ those soon to be enhanced rcfile integrity checks, ]
[ achieved through several of the following patches. ]
Reference(s):
. original qualys patch
0097-top-Do-not-default-to-the-cwd-in-configs_read.patch
commit b45c4803dd
Signed-off-by: Jim Warner <james.warner@comcast.net>
As comm length can be longer than 15 characters with newer kernels, it
doesn't make sense to have a warning when you make the match string
longer than this.
As a side-effect, it removes the false-positive you got when you used
long regex matches (see issue #92 )
References:
commit 2cfdbbe897procps-ng/procps#92
For many years, the comm length has been set to 16. Previously to that
it was 8. This means for things like kworkers they all have very cryptic
names. The kernel is now going to increase this size to 64, so the
procps library will follow this length increase.
System tools may also increase their default length to 64, or keep it at
16; there is only so much screen real estate.
References:
https://lkml.org/lkml/2018/5/17/16
This patch prevents three problems:
1/ Because snprintf() returns "the number of characters (excluding the
terminating null byte) which would have been written to the final string
if enough space had been available", _eol may point past the end of _str
and write out-of-bounds (in Batch mode).
2/ _eol is never checked against _str, so "while (*(--_eol) == ' ');"
may point _eol below _str and write out-of-bounds (in Batch mode).
3/ Sanity-check Pseudo_row to protect the strcpy().
This patch fixes two problems:
1/ In the switch case 0, if sub_end is at the very end of lin[], the two
null-byte writes are off-by-two (a stack-based buffer overflow). Replace
this end-of-string "emulation" with an equivalent test on ch (and then
goto/break out of the loop).
2/ "sub_end += 2" jumps over the null-byte terminator in lin[] if the
line contains a raw (without a tilde) \001-\010 character. Detect such a
null-byte terminator and goto/break out of the loop.
Note: in the case of a raw \001-\010 character, the character at
"sub_end + 1" is never processed (it is skipped/jumped over); this is
not a security problem anymore (since 2/ was fixed), so we decided not
to change this behavior, for backward-compatibility.
- Make sure i is at least 1 before "i - 1" and "--i".
- Initialize endpflg (to 0, as it was originally, since it is static)
before the "for" loop (the "break" may leave endpflg uninitialized,
for example).
pflgsall[] can contain PFLAGSSIZ = 100 elements, each iteration of the
loop can write 3 elements to pflgsall[], and there are EU_MAXPFLGS = 58
iterations: a buffer overflow (it can be triggered via the configuration
file, for example, by filling "fieldscur" with the "sortindx" flag).
The safety of the critical function task_show() depends on the sanity of
Screen_cols. Just copy the tests on w_cols to Screen_cols (from the same
function adj_geometry()).
Several of these buffer overflows can actually be triggered (through the
configuration file for example): in config_file(), inspection_utility(),
and show_special().
The default action for SIGURG is to ignore the signal, for example.
This is very similar to the patch "ps/display.c: Always exit from
signal_handler()."
Every time sortindx is used as an index, or loaded from the
configuration file. Otherwise it leads to out-of-bounds reads and
arbitrary code execution.
Otherwise they may lead to out-of-bounds writes (snprintf() returns the
number of characters which would have been written if enough space had
been available).
Also, make sure buf is null-terminated after COLPLUSCH has been written.
Otherwise they lead to out-of-bounds reads and format-string bugs.
Since these variables are set/written to in several places (for example,
config_file()), check them in the only place where they are read/used.
Also, constify the static gtab[]s.
If the HOME environment variable is not set, or not absolute, use the
home directory returned by getpwuid(getuid()), if set and absolute
(instead of the cwd "."); otherwise, set p_home to NULL.
To keep the changes to a minimum, we rely on POSIX, which requires that
fopen() fails with ENOENT if the pathname (Rc_name) is an empty string.
This integrates well into the existing code, and makes write_rcfile()
work without a change.
Also, it makes the code in configs_read() easier to follow: only set and
use p_home if safe, and only set Rc_name if safe (in all the other cases
it is the empty string, and the fopen() calls fail). Plus, check for
snprintf() truncation (and if it happens, reset Rc_name to the empty
string).
Important note: top.1 should probably be updated, since it mentions the
fallback to the current working directory.
Check the return value of sscanf() to make sure that all input items are
properly initialized.
In extended mode (x_option), one_proc() loads the values of start and
perms during one iteration of the while loop, and displays them during
one of the following iterations, but start and perms are variables local
to the while loop: move them out of the while loop, to the beginning of
the function.
Also, display a mapping only if cp2 is properly initialized; otherwise
(for example), mappings that do not belong to a selected range are
displayed, and with a NULL mapping name:
$ pmap -x -A 6FFF00000000,7FFF00000000 $$
...
Address Kbytes RSS Dirty Mode Mapping
000055b3d1e9b000 0 912 0 r-xp (null)
000055b3d2194000 0 16 16 r--p (null)
000055b3d2198000 0 36 36 rw-p (null)
...
If "cp = strrchr(mapbuf_b, '/')" then this function returns, and
otherwise there is no '/' in mapbuf_b and "cp = strchr(mapbuf_b, '/')"
is always false: remove this second block, since it is never entered.
Also, constify a few things in this function.
In the headers, the space was misplaced; for example, "pmap -XX $$"
outputs "VmFlagsMapping" (without a space). Use justify_print() instead
of printf().
There was also an extra space in the output, because vmflags[] (from the
"VmFlags:" line) always ends with a space. Overwriting this last space
with a null byte fixes this misalignment.
vmflags[] is a 27*(2+1)=81 char array, but there are 30 flags now (not
27), and even with 27 flags this was an off-by-one overflow (the kernel
always outputs a flag with "%c%c ", so the last +1 is for a space, not
for the terminating null byte). Protect vmflags[] with a maximum field
width, as in the surrounding sscanf() calls.
Otherwise "the contents of the array remain unchanged and a null pointer
is returned" or "the array contents are indeterminate and a null pointer
is returned".
readeither() caches (in new_p) a pointer to the proc_t of a task-group
leader, but readeither()'s callers can do pretty much anything with the
proc_t structure passed to and/or returned by this function. For
example, they can 1/ free it or 2/ recycle it (by passing it to
readeither() as x).
1/ leads to a use-after-free, and 2/ leads to unexpected behavior when
taskreader()/simple_readtask() is called with new_p equal to x (this is
not a theoretical flaw: 2/ happens in readproctab3() when want_task()
returns false and p is a group leader).
As a workaround, we keep a copy of new_p's first member (tid) in static
storage, and the next times we enter readeither() we check this "canary"
against the tid in new_p: if they differ, we reset new_p to NULL, which
forces the allocation of a new proc_t (the new "leader", or reference).
This always detects 2/ (because free_acquired(x,1) memsets x and hence
new_p); always detects 1/ if freed via free_acquired() and/or freeproc()
(very likely, otherwise memory may be leaked); probably detects 1/ even
if freed directly via free() (because the canary is the first member of
proc_t, likely to be overwritten by free()); but can not detect 1/ if
free() does not write to new_p's chunk at all.
Moreover, accessing new_p->tid to check the canary in case 1/ is itself
a use-after-free, so a better long-term solution should be implemented
at some point (we wanted to avoid intrusive and backward-incompatible
changes in this library function, hence this imperfect workaround).
If an integer overflow is about to be reached, call xalloc_err_handler()
(since it would have been caught by calloc() or reallocarray()) and then
exit(): these integer overflows are far from reachable, with the current
PID_MAX_LIMIT (2^22), so if they are there is something very wrong going
on. Note: we check the n_*alloc variables against INT_MAX even when they
are size_t because they are later stored as int in a struct proc_data_t.
If QUICK_THREADS is not defined (it is not by default, but most
distributions enable it) and task_dir_missing is true (only on very old
kernels), then readtask() forgets to reset some of the struct proc_t t's
members, which later results in double-free()s in free_acquired().
For now, we simply synchronized the list of members to be reset with the
list of members freed in free_acquired().
The memset() in the PROC_LOOSE_TASKS loop leaves a struct proc_t
uninitialized (the one at data+n_used), which leads to a use-after-free.
ps calls readproctab2(), but only if !TF_loose_tasks, and this U-A-F is
triggered only if PROC_LOOSE_TASKS, so there seems to be no vulnerable
call in the procps package itself (other users of the libprocps may be
vulnerable, though).
Check the return value of snprintf(), otherwise dst may point
out-of-bounds when it reaches the end of the dst_buffer (the snprintf()
always returns 1 in that case, even if there is not enough space left),
and vMAX becomes negative and is passed to snprintf() as a size_t.