Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
/callgrind/tests/find_debuginfo
/callgrind/tests/runtime_obj_skip_c
/callgrind/tests/runtime_obj_skip_underflow
/callgrind/tests/prctl_get_auxv
/callgrind/tests/*.so
/callgrind/tests/out_dbg
/callgrind/tests/test-suite-overall.log
Expand Down
5 changes: 5 additions & 0 deletions callgrind/tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ EXTRA_DIST = \
allow_file_caps.stdout.exp \
allow_file_caps_refused.vgtest allow_file_caps_refused.stderr.exp \
allow_file_caps_refused.stdout.exp \
prctl_get_auxv.vgtest prctl_get_auxv.stderr.exp \
prctl_get_auxv.stdout.exp \
find_debuginfo.vgtest find_debuginfo.stderr.exp find_debuginfo.post.exp \
runtime_obj_skip_py.vgtest runtime_obj_skip_py.stderr.exp runtime_obj_skip_py.post.exp \
runtime_obj_skip_py.py runtime_obj_skip_py_shim.c \
Expand All @@ -43,6 +45,9 @@ EXTRA_DIST = \
inline-crossfile-helper1.h inline-crossfile-helper2.h filter_inline

check_PROGRAMS = clreq register_desc register_desc_threads find_debuginfo simwork threads inline-samefile inline-crossfile runtime_obj_skip_c runtime_obj_skip_underflow
if HAVE_PR_GET_AUXV
check_PROGRAMS += prctl_get_auxv
endif

AM_CFLAGS += $(AM_FLAG_M3264_PRI)
AM_CXXFLAGS += $(AM_FLAG_M3264_PRI)
Expand Down
133 changes: 133 additions & 0 deletions callgrind/tests/prctl_get_auxv.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* prctl(PR_GET_AUXV) must return the program's own auxv, the one on its
stack that getauxval() reads, not the auxv of the Valgrind tool binary
the kernel actually exec'd, and otherwise behave like the kernel.

Run natively, --kernel-size prints the size the kernel returns. The
test itself takes that kernel size as its only argument. */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>

static const char *base_name(const char *path)
{
const char *slash = strrchr(path, '/');
return slash ? slash + 1 : path;
}

static int has_only_byte(const void *start, size_t len, unsigned char byte)
{
const unsigned char *p = start;
for (size_t i = 0; i < len; i++)
if (p[i] != byte)
return 0;
return 1;
}

static void print_error(const char *what, long res, int err,
int expected_err, const char *expected_name)
{
printf("%s: %s\n", what,
res == -1 && err == expected_err ? expected_name : "not rejected");
}

int main(int argc, char **argv, char **envp)
{
if (argc > 1 && strcmp(argv[1], "--kernel-size") == 0) {
printf("%ld\n", (long)prctl(PR_GET_AUXV, 0, 0, 0, 0));
return 0;
}
if (argc != 2) {
fprintf(stderr, "usage: %s KERNEL_SIZE\n", argv[0]);
return 2;
}
long kernel_size = strtol(argv[1], NULL, 10);

/* The auxv starts right after the NULL that ends envp. */
char **env_end = envp;
while (*env_end != NULL)
env_end++;
unsigned long *stack_auxv = (unsigned long *)(env_end + 1);
size_t stack_auxv_size = 2 * sizeof(unsigned long);
for (const unsigned long *p = stack_auxv; p[0] != AT_NULL; p += 2)
stack_auxv_size += 2 * sizeof(unsigned long);

unsigned long buf[512];
memset(buf, 0xaa, sizeof(buf));
long size = prctl(PR_GET_AUXV, buf, sizeof(buf), 0, 0);
if (size < 0) {
perror("prctl(PR_GET_AUXV)");
return 1;
}

for (const unsigned long *p = buf; p[0] != AT_NULL; p += 2)
if (p[0] == AT_EXECFN)
printf("AT_EXECFN: %s\n", base_name((const char *)p[1]));

printf("size: %s\n", size == kernel_size ? "same as the kernel's"
: "differs from the kernel's");

int is_size_plausible = (size_t)size >= stack_auxv_size
&& (size_t)size <= sizeof(buf);
int matches_stack = is_size_plausible
&& memcmp(buf, stack_auxv, stack_auxv_size) == 0;
printf("matches the auxv on the stack: %s\n", matches_stack ? "yes" : "no");

const char *bytes = (const char *)buf;
int is_zero_padded = is_size_plausible
&& has_only_byte(bytes + stack_auxv_size, size - stack_auxv_size, 0)
&& has_only_byte(bytes + size, sizeof(buf) - size, 0xaa);
printf("zero padding up to the returned size: %s\n",
is_zero_padded ? "yes" : "no");

/* The kernel answers from the auxv it saved at exec, so later writes
to the copy on the stack do not show. */
unsigned long first_value = stack_auxv[1];
stack_auxv[1] = ~first_value;
unsigned long later[512];
long later_size = prctl(PR_GET_AUXV, later, sizeof(later), 0, 0);
stack_auxv[1] = first_value;
int is_saved_copy = later_size == size
&& memcmp(later, buf, stack_auxv_size) == 0;
printf("stack auxv modified: %s\n",
is_saved_copy ? "saved vector returned" : "modified vector returned");

unsigned long small[4], fill;
memset(small, 0xaa, sizeof(small));
memset(&fill, 0xaa, sizeof(fill));
long small_size = prctl(PR_GET_AUXV, small, 2 * sizeof(unsigned long), 0, 0);
int is_truncated = small_size == size
&& memcmp(small, buf, 2 * sizeof(unsigned long)) == 0
&& small[2] == fill && small[3] == fill;
printf("short buffer: %s\n",
is_truncated ? "full size returned, rest untouched" : "wrong");

long res;
errno = 0;
res = prctl(PR_GET_AUXV, buf, sizeof(buf), 1, 0);
print_error("nonzero arg4", res, errno, EINVAL, "EINVAL");

errno = 0;
res = prctl(PR_GET_AUXV, buf, sizeof(buf), 0, 1);
print_error("nonzero arg5", res, errno, EINVAL, "EINVAL");

errno = 0;
res = prctl(PR_GET_AUXV, (void *)8, sizeof(buf), 0, 0);
print_error("unmapped buffer", res, errno, EFAULT, "EFAULT");

void *read_only = mmap(NULL, sizeof(buf), PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (read_only == MAP_FAILED) {
perror("mmap");
return 1;
}
errno = 0;
res = prctl(PR_GET_AUXV, read_only, sizeof(buf), 0, 0);
print_error("read-only buffer", res, errno, EFAULT, "EFAULT");

return 0;
}
Empty file.
10 changes: 10 additions & 0 deletions callgrind/tests/prctl_get_auxv.stdout.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
AT_EXECFN: prctl_get_auxv
size: same as the kernel's
matches the auxv on the stack: yes
zero padding up to the returned size: yes
stack auxv modified: saved vector returned
short buffer: full size returned, rest untouched
nonzero arg4: EINVAL
nonzero arg5: EINVAL
unmapped buffer: EFAULT
read-only buffer: EFAULT
5 changes: 5 additions & 0 deletions callgrind/tests/prctl_get_auxv.vgtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
prereq: test -x prctl_get_auxv && ../../tests/os_test linux 6.4.0
prog: prctl_get_auxv
args: `./prctl_get_auxv --kernel-size`
vgopts: -q --callgrind-out-file=callgrind.out.prctl_get_auxv
cleanup: rm -f callgrind.out.prctl_get_auxv
15 changes: 15 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2187,6 +2187,21 @@ AC_MSG_RESULT([no])

AM_CONDITIONAL(HAVE_NR_IO_PGETEVENTS, [test x$ac_have_nr_io_pgetevents = xyes])

AC_MSG_CHECKING([for PR_GET_AUXV])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <sys/prctl.h>
]], [[
return PR_GET_AUXV
]])], [
ac_have_pr_get_auxv=yes
AC_MSG_RESULT([yes])
], [
ac_have_pr_get_auxv=no
AC_MSG_RESULT([no])
])

AM_CONDITIONAL(HAVE_PR_GET_AUXV, [test x$ac_have_pr_get_auxv = xyes])

#----------------------------------------------------------------------------
# Checking for supported compiler flags.
#----------------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions coregrind/m_clientstate.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ SizeT VG_(clstk_max_size) = 0;
initimg-{linux,solaris}.c. */
UWord* VG_(client_auxv) = NULL;

/* Solaris and Linux only, a startup copy of the client auxv. */
const UWord* VG_(client_saved_auxv) = NULL;
SizeT VG_(client_saved_auxv_size) = 0;

Addr VG_(brk_base) = 0; /* start of brk */
Addr VG_(brk_limit) = 0; /* current brk */

Expand Down
5 changes: 5 additions & 0 deletions coregrind/m_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1745,6 +1745,11 @@ Int valgrind_main ( Int argc, HChar **argv, HChar **envp )
}
client_auxv_len += 2 * sizeof(UWord);

UWord *saved_auxv = VG_(malloc)("main.saved_auxv", client_auxv_len);
VG_(memcpy)(saved_auxv, VG_(client_auxv), client_auxv_len);
VG_(client_saved_auxv) = saved_auxv;
VG_(client_saved_auxv_size) = client_auxv_len;

VG_(write)(fd, VG_(client_auxv), client_auxv_len);

/* Don't bother to seek the file back to the start; instead do
Expand Down
43 changes: 43 additions & 0 deletions coregrind/m_syswrap/syswrap-linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,46 @@ PRE(sys_prctl)
case VKI_PR_CAPBSET_DROP:
PRE_REG_READ2(int, "prctl", int, option, int, capability);
break;
case VKI_PR_GET_AUXV: {
PRE_REG_READ5(long, "prctl", int, option, void *, addr,
unsigned long, size, unsigned long, arg4,
unsigned long, arg5);
/* The kernel would return the auxv it saved when it exec'd the
Valgrind tool. Return the client's auxv instead, from the copy
saved at startup: the vector getauxval() and /proc/self/auxv
report, read without touching the client stack.
A call completed in the pre-handler must not set SfMayBlock. */
*flags &= ~SfMayBlock;
if (ARG4 != 0 || ARG5 != 0) {
SET_STATUS_Failure(VKI_EINVAL);
break;
}
/* With a zero size the kernel copies nothing and returns the size
of its saved auxv, or fails if it does not know PR_GET_AUXV. */
SysRes kres = VG_(do_syscall5)(__NR_prctl, VKI_PR_GET_AUXV, 0, 0, 0, 0);
if (sr_isError(kres)) {
SET_STATUS_from_SysRes(kres);
break;
}
vg_assert(VG_(client_saved_auxv) != NULL);
SizeT auxv_size = VG_(client_saved_auxv_size);
/* Like the kernel, report the size of a zero-padded buffer at least
as large as the auxv, and copy as much of it as fits. */
SizeT total = VG_MAX(sr_Res(kres), auxv_size);
SizeT len = VG_MIN(total, ARG3);
if (len > 0) {
PRE_MEM_WRITE("prctl(get-auxv)", ARG2, len);
if (!VG_(am_is_valid_for_client)((Addr)ARG2, len, VKI_PROT_WRITE)) {
SET_STATUS_Failure(VKI_EFAULT);
break;
}
SizeT copied = VG_MIN(len, auxv_size);
VG_(memcpy)((void *)(Addr)ARG2, VG_(client_saved_auxv), copied);
VG_(memset)((HChar *)(Addr)ARG2 + copied, 0, len - copied);
}
SET_STATUS_Success(total);
break;
}
default:
PRE_REG_READ5(long, "prctl",
int, option, unsigned long, arg2, unsigned long, arg3,
Expand Down Expand Up @@ -1612,6 +1652,9 @@ POST(sys_prctl)
case VKI_PR_GET_ENDIAN:
POST_MEM_WRITE(ARG2, sizeof(Int));
break;
case VKI_PR_GET_AUXV:
POST_MEM_WRITE(ARG2, VG_MIN(RES, ARG3));
break;
case VKI_PR_SET_NAME:
{
const HChar* new_name = (const HChar*) (Addr)ARG2;
Expand Down
6 changes: 6 additions & 0 deletions coregrind/pub_core_clientstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ extern SizeT VG_(clstk_max_size); // max size of the main threads's client stack
or initimg-solaris.c, respectively. */
extern UWord* VG_(client_auxv);

/* Linux and Solaris only: a copy of the client auxv taken at startup,
which later changes to the client stack cannot affect. The size is in
bytes and includes the terminating AT_NULL entry. */
extern const UWord* VG_(client_saved_auxv);
extern SizeT VG_(client_saved_auxv_size);

extern Addr VG_(brk_base); // start of brk
extern Addr VG_(brk_limit); // current brk

Expand Down
2 changes: 2 additions & 0 deletions include/vki/vki-linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,8 @@ struct vki_vt_consize {
#define VKI_PR_SET_TAGGED_ADDR_CTRL 55
#define VKI_PR_GET_TAGGED_ADDR_CTRL 56

#define VKI_PR_GET_AUXV 0x41555856

//----------------------------------------------------------------------
// From linux-2.6.19/include/linux/usbdevice_fs.h
//----------------------------------------------------------------------
Expand Down
Loading