Bug 381304 - RFE: --track-origins=yes identifies system call source of Uninitialized value
Summary: RFE: --track-origins=yes identifies system call source of Uninitialized value
Status: REPORTED
Alias: None
Product: valgrind
Classification: Developer tools
Component: memcheck (other bugs)
Version First Reported In: 3.13 SVN
Platform: Other Linux
: NOR wishlist
Target Milestone: ---
Assignee: Julian Seward
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2017-06-17 04:57 UTC by John Reiser
Modified: 2023-01-22 23:47 UTC (History)
2 users (show)

See Also:
Latest Commit:
Version Fixed/Implemented In:
Sentry Crash Report:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description John Reiser 2017-06-17 04:57:26 UTC
Request for enhancement (RFE): memcheck --track-origins=yes should report the system call name (or number) if the "Uninitialised value was created" by a system call.  Currently the message gives the program counter value.  In general for most compiler-written code this is sufficient to identify the source code.

However, for system calls there is an implementation style which sets up the arguments separately for each call, then merges all syscalls into one tail.  This style makes it easy to track all system calls because there is only one location which makes system calls.  Unfortunately the program counter printed by memcheck no longer identifies which syscall:

==25006== Conditional jump or move depends on uninitialised value(s)
==25006==    at 0xF17E32: ???
==25006==  Uninitialised value was created
==25006==    at 0xF169C6: ???
==25006==

and 0xF169C6 is the address of the instruction after the one-and-only syscall instruction, and is the same for any system call.
Comment 1 Philippe Waroquiers 2017-06-22 21:20:59 UTC
Do you have a reproducer ?
And/or a list of syscalls that can produce uninit data ?

Thanks
Comment 2 John Reiser 2017-06-22 22:17:54 UTC
The system routines in musl libc (https://www.musl-libc.org/) are coded with the system call instruction as a common tail.

The system call which produces uninit values is brk()/sbrk(), which libmusl invokes from malloc().  New pages from the OS are known to be 0, but "re-alloc()ed" pages [sbrk() of a positive increment after sbrk() of a negative increment; or a sequence of brk() whose arguments are not monotonically non-decreasing] need not be 0, and must be treated as uninit.  Proving that nobody else touched the new pages (thus that they are still all initialized [namely, to zero]) can be tricky; see https://bugs.kde.org/show_bug.cgi?id=381299 .

The socket() system calls is a multiplexed group of syscalls where the interpretation of arguments depends heavily on flags.  Many fields are defined only in some circumstances, and there are many cases.  When combined with a malloc() that does not integrate with memcheck (see https://bugs.kde.org/show_bug.cgi?id=381326) then it can look like socket() is the source of uninit.

I ran into this problem while working on https://github.com/upx/upx/issues/105 .  If still available, then the link https://www.dropbox.com/s/s6f6agxsjo32zzt/Uw32f.tar.gz?dl=0 (of 2017-06-16 or so) provides a pre-compiled i386 program with symbols where memcheck complains many times about socket() calls.  [I have run the program many times, both bare and under memcheck; I believe it is safe to run.]  It looks now like libmusl __malloc0() is the ultimate cause of all the reported problems, but it took a while to figure that out.  It would have taken much less time if brk() had been identified in [all] the memcheck reports.
Comment 3 John Reiser 2017-06-23 05:02:21 UTC
Another syscall that produces uninit is readlink().  The portion of the result buffer that is beyond the returned length, should be regarded as Uninit.  There is no guarantee that the kernel avoids writing into any portion of the whole buffer, although all known implementations write only an initial string [non-terminated] of the returned length.   [memcheck 3.13.0 implements the observed behavior, which is a risk for "false negative" complaints.]

link_length = readlink(pathname, buf, buflen)
if (link_length > 0) {  // no error
    // buf[link_length, buflen) should be regarded as Uninit.
}
Comment 4 John Reiser 2017-06-23 05:12:44 UTC
read_length = read(fd, buf, buflen) should regard buf[read_length, buflen) as Uninit when there is no error.  When there is an error, then ALL of buf[0, buflen) should be regarded as Uninit.  This is particularly true for character special devices: tty, "raw" tape drive, etc.  These devices can misbehave readily.
Comment 5 John Reiser 2017-06-23 05:22:16 UTC
(In reply to John Reiser from comment #3)
> There is no guarantee that the kernel avoids writing into any
> portion of the whole buffer, although all known implementations write only
> an initial string [non-terminated] of the returned length.

One easily-imagined implementation technique would be for the kernel to build the result in the output buffer incrementally, path-component by path-component.  If the last path component is a symlink to a short absolute path, then the result can be shorter than the preceding intermediate step.