/* * Check decoding of select/_newselect syscalls. * * Copyright (c) 2015-2018 Dmitry V. Levin * Copyright (c) 2015-2017 The strace developers. * All rights reserved. * * SPDX-License-Identifier: GPL-2.0-or-later */ /* * Based on test by Dr. David Alan Gilbert */ #include #include #include #include #include #include #include static const char *errstr; static long xselect(const kernel_ulong_t nfds, const kernel_ulong_t rs, const kernel_ulong_t ws, const kernel_ulong_t es, const kernel_ulong_t tv) #ifndef xselect { long rc = syscall(TEST_SYSCALL_NR, F8ILL_KULONG_MASK | nfds, rs, ws, es, tv); errstr = sprintrc(rc); return rc; } #else ; #endif #define XSELECT(expected_, ...) \ do { \ long rc = xselect(__VA_ARGS__); \ if (rc != (expected_)) \ perror_msg_and_fail(TEST_SYSCALL_STR \ ": expected %d" \ ", returned %ld", \ (expected_), rc); \ } while (0) \ /* End of XSELECT definition. */ int main(void) { #ifdef PATH_TRACING_FD skip_if_unavailable("/proc/self/fd/"); #endif for (int i = 3; i < FD_SETSIZE; ++i) { #ifdef PATH_TRACING_FD if (i == PATH_TRACING_FD) continue; #endif (void) close(i); } int fds[2]; if (pipe(fds)) perror_msg_and_fail("pipe"); static const int smallset_size = sizeof(kernel_ulong_t) * 8; const int nfds = fds[1] + 1; if (nfds > smallset_size) error_msg_and_fail("nfds[%d] > smallset_size[%d]\n", nfds, smallset_size); struct timeval tv_in = { 0, 123 }; struct timeval *const tv = tail_memdup(&tv_in, sizeof(tv_in)); const uintptr_t a_tv = (uintptr_t) tv; TAIL_ALLOC_OBJECT_VAR_PTR(kernel_ulong_t, l_rs); fd_set *const rs = (void *) l_rs; const uintptr_t a_rs = (uintptr_t) rs; TAIL_ALLOC_OBJECT_VAR_PTR(kernel_ulong_t, l_ws); fd_set *const ws = (void *) l_ws; const uintptr_t a_ws = (uintptr_t) ws; TAIL_ALLOC_OBJECT_VAR_PTR(kernel_ulong_t, l_es); fd_set *const es = (void *) l_es; const uintptr_t a_es = (uintptr_t) es; long rc; /* * An equivalent of nanosleep. */ if (xselect(0, 0, 0, 0, a_tv)) { if (errno == ENOSYS) perror_msg_and_skip(TEST_SYSCALL_STR); else perror_msg_and_fail(TEST_SYSCALL_STR); } #ifndef PATH_TRACING_FD printf("%s(0, NULL, NULL, NULL, {tv_sec=%lld, tv_usec=%llu})" " = 0 (Timeout)\n", TEST_SYSCALL_STR, (long long) tv_in.tv_sec, zero_extend_signed_to_ull(tv_in.tv_usec)); #endif /* EFAULT on tv argument */ XSELECT(-1, 0, 0, 0, 0, a_tv + 1); #ifndef PATH_TRACING_FD printf("%s(0, NULL, NULL, NULL, %#lx) = %s\n", TEST_SYSCALL_STR, (unsigned long) a_tv + 1, errstr); #endif /* * Start with a nice simple select with the same set. */ for (int i = nfds; i <= smallset_size; ++i) { *l_rs = (1UL << fds[0]) | (1UL << fds[1]); XSELECT(1, i, a_rs, a_rs, a_rs, 0); #ifndef PATH_TRACING_FD printf("%s(%d, [%d %d], [%d %d], [%d %d], NULL) = 1 ()\n", TEST_SYSCALL_STR, i, fds[0], fds[1], fds[0], fds[1], fds[0], fds[1]); #else *l_rs = (1UL << fds[0]) | (1UL << fds[1]) | (1UL << PATH_TRACING_FD); XSELECT(i > PATH_TRACING_FD ? 3 : 1, i, a_rs, a_rs, a_rs, 0); if (i > PATH_TRACING_FD) { printf("%s(%d, [%d %d %d], [%d %d %d], [%d %d %d]" ", NULL) = 3 ()\n", TEST_SYSCALL_STR, i, fds[0], fds[1], PATH_TRACING_FD, fds[0], fds[1], PATH_TRACING_FD, fds[0], fds[1], PATH_TRACING_FD); } #endif } /* * Odd timeout. */ *l_rs = (1UL << fds[0]) | (1UL << fds[1]); tv_in.tv_sec = 0xdeadbeefU; tv_in.tv_usec = 0xfacefeedU; memcpy(tv, &tv_in, sizeof(tv_in)); rc = xselect(nfds, a_rs, a_rs, a_rs, a_tv); if (rc < 0) { #ifndef PATH_TRACING_FD printf("%s(%d, [%d %d], [%d %d], [%d %d]" ", {tv_sec=%lld, tv_usec=%llu}) = %s\n", TEST_SYSCALL_STR, nfds, fds[0], fds[1], fds[0], fds[1], fds[0], fds[1], (long long) tv_in.tv_sec, zero_extend_signed_to_ull(tv_in.tv_usec), errstr); #endif /* !PATH_TRACING_FD */ } else { #ifndef PATH_TRACING_FD printf("%s(%d, [%d %d], [%d %d], [%d %d]" ", {tv_sec=%lld, tv_usec=%llu}) = %ld" " (left {tv_sec=%lld, tv_usec=%llu})\n", TEST_SYSCALL_STR, nfds, fds[0], fds[1], fds[0], fds[1], fds[0], fds[1], (long long) tv_in.tv_sec, zero_extend_signed_to_ull(tv_in.tv_usec), rc, (long long) tv->tv_sec, zero_extend_signed_to_ull(tv->tv_usec)); #endif /* !PATH_TRACING_FD */ } /* * Very odd timeout. */ *l_rs = (1UL << fds[0]) | (1UL << fds[1]); tv_in.tv_sec = (time_t) 0xcafef00ddeadbeefLL; tv_in.tv_usec = (suseconds_t) 0xbadc0dedfacefeedLL; memcpy(tv, &tv_in, sizeof(tv_in)); rc = xselect(nfds, a_rs, a_rs, a_rs, a_tv); if (rc < 0) { #ifndef PATH_TRACING_FD printf("%s(%d, [%d %d], [%d %d], [%d %d]" ", {tv_sec=%lld, tv_usec=%llu}) = %s\n", TEST_SYSCALL_STR, nfds, fds[0], fds[1], fds[0], fds[1], fds[0], fds[1], (long long) tv_in.tv_sec, zero_extend_signed_to_ull(tv_in.tv_usec), errstr); #endif /* PATH_TRACING_FD */ } else { #ifndef PATH_TRACING_FD printf("%s(%d, [%d %d], [%d %d], [%d %d]" ", {tv_sec=%lld, tv_usec=%llu}) = %ld" " (left {tv_sec=%lld, tv_usec=%llu})\n", TEST_SYSCALL_STR, nfds, fds[0], fds[1], fds[0], fds[1], fds[0], fds[1], (long long) tv_in.tv_sec, zero_extend_signed_to_ull(tv_in.tv_usec), rc, (long long) tv->tv_sec, zero_extend_signed_to_ull(tv->tv_usec)); #endif /* PATH_TRACING_FD */ } /* * Another simple one, with a timeout. */ for (int i = nfds; i <= smallset_size; ++i) { *l_rs = (1UL << fds[0]) | (1UL << fds[1]); *l_ws = (1UL << 1) | (1UL << 2) | (1UL << fds[0]) | (1UL << fds[1]); *l_es = 0; tv_in.tv_sec = 0xc0de1; tv_in.tv_usec = 0xc0de2; memcpy(tv, &tv_in, sizeof(tv_in)); XSELECT(3, i, a_rs, a_ws, a_es, a_tv); #ifndef PATH_TRACING_FD printf("%s(%d, [%d %d], [%d %d %d %d], []" ", {tv_sec=%lld, tv_usec=%llu}) = 3 (out [1 2 %d]" ", left {tv_sec=%lld, tv_usec=%llu})\n", TEST_SYSCALL_STR, i, fds[0], fds[1], 1, 2, fds[0], fds[1], (long long) tv_in.tv_sec, zero_extend_signed_to_ull(tv_in.tv_usec), fds[1], (long long) tv->tv_sec, zero_extend_signed_to_ull(tv->tv_usec)); #else *l_rs = (1UL << fds[0]) | (1UL << fds[1]) | (1UL << PATH_TRACING_FD); *l_ws = (1UL << 1) | (1UL << 2) | (1UL << fds[0]) | (1UL << fds[1]); tv_in.tv_sec = 0xc0de1; tv_in.tv_usec = 0xc0de2; memcpy(tv, &tv_in, sizeof(tv_in)); XSELECT(3 + (i > PATH_TRACING_FD), i, a_rs, a_ws, a_es, a_tv); if (i > PATH_TRACING_FD) { printf("%s(%d, [%d %d %d], [%d %d %d %d], []" ", {tv_sec=%lld, tv_usec=%llu})" " = 4 (in [%d], out [1 2 %d]" ", left {tv_sec=%lld, tv_usec=%llu})\n", TEST_SYSCALL_STR, i, fds[0], fds[1], PATH_TRACING_FD, 1, 2, fds[0], fds[1], (long long) tv_in.tv_sec, zero_extend_signed_to_ull(tv_in.tv_usec), PATH_TRACING_FD, fds[1], (long long) tv->tv_sec, zero_extend_signed_to_ull(tv->tv_usec)); } *l_rs = (1UL << fds[0]) | (1UL << fds[1]); *l_ws = (1UL << 1) | (1UL << 2) | (1UL << fds[0]) | (1UL << fds[1]) | (1UL << PATH_TRACING_FD); tv_in.tv_sec = 0xc0de1; tv_in.tv_usec = 0xc0de2; memcpy(tv, &tv_in, sizeof(tv_in)); XSELECT(3 + (i > PATH_TRACING_FD), i, a_rs, a_ws, a_es, a_tv); if (i > PATH_TRACING_FD) { printf("%s(%d, [%d %d], [%d %d %d %d %d], []" ", {tv_sec=%lld, tv_usec=%llu})" " = 4 (out [1 2 %d %d]" ", left {tv_sec=%lld, tv_usec=%llu})\n", TEST_SYSCALL_STR, i, fds[0], fds[1], 1, 2, fds[0], fds[1], PATH_TRACING_FD, (long long) tv_in.tv_sec, zero_extend_signed_to_ull(tv_in.tv_usec), fds[1], PATH_TRACING_FD, (long long) tv->tv_sec, zero_extend_signed_to_ull(tv->tv_usec)); } *l_rs = (1UL << fds[0]) | (1UL << fds[1]); *l_ws = (1UL << 1) | (1UL << 2) | (1UL << fds[0]) | (1UL << fds[1]); *l_es = (1UL << PATH_TRACING_FD); tv_in.tv_sec = 0xc0de1; tv_in.tv_usec = 0xc0de2; memcpy(tv, &tv_in, sizeof(tv_in)); XSELECT(3, i, a_rs, a_ws, a_es, a_tv); if (i > PATH_TRACING_FD) { printf("%s(%d, [%d %d], [%d %d %d %d], [%d]" ", {tv_sec=%lld, tv_usec=%llu}) = 3 (out [1 2 %d]" ", left {tv_sec=%lld, tv_usec=%llu})\n", TEST_SYSCALL_STR, i, fds[0], fds[1], 1, 2, fds[0], fds[1], PATH_TRACING_FD, (long long) tv_in.tv_sec, zero_extend_signed_to_ull(tv_in.tv_usec), fds[1], (long long) tv->tv_sec, zero_extend_signed_to_ull(tv->tv_usec)); } #endif /* PATH_TRACING_FD */ } /* * Now the crash case that trinity found, negative nfds * but with a pointer to a large chunk of valid memory. */ static fd_set set[0x1000000 / sizeof(fd_set)]; FD_SET(fds[1], set); XSELECT(-1, -1U, 0, (uintptr_t) set, 0, 0); #ifndef PATH_TRACING_FD printf("%s(-1, NULL, %p, NULL, NULL) = %s\n", TEST_SYSCALL_STR, set, errstr); #endif /* * Big sets, nfds exceeds FD_SETSIZE limit. */ const size_t big_size = sizeof(fd_set) + sizeof(long); fd_set *const big_rs = tail_alloc(big_size); const uintptr_t a_big_rs = (uintptr_t) big_rs; fd_set *const big_ws = tail_alloc(big_size); const uintptr_t a_big_ws = (uintptr_t) big_ws; for (unsigned int i = FD_SETSIZE; i <= big_size * 8; ++i) { memset(big_rs, 0, big_size); memset(big_ws, 0, big_size); FD_SET(fds[0], big_rs); tv->tv_sec = 0; tv->tv_usec = 10 + (i - FD_SETSIZE); XSELECT(0, i, a_big_rs, a_big_ws, 0, a_tv); #ifndef PATH_TRACING_FD printf("%s(%d, [%d], [], NULL, {tv_sec=0, tv_usec=%d})" " = 0 (Timeout)\n", TEST_SYSCALL_STR, i, fds[0], 10 + (i - FD_SETSIZE)); #else FD_SET(fds[0], big_rs); FD_SET(PATH_TRACING_FD, big_rs); tv->tv_sec = 0; tv->tv_usec = 10 + (i - FD_SETSIZE); XSELECT(1, i, a_big_rs, a_big_ws, 0, a_tv); printf("%s(%d, [%d %d], [], NULL, {tv_sec=0, tv_usec=%d})" " = 1 (in [%d], left {tv_sec=0, tv_usec=%llu})\n", TEST_SYSCALL_STR, i, fds[0], PATH_TRACING_FD, 10 + (i - FD_SETSIZE), PATH_TRACING_FD, zero_extend_signed_to_ull(tv->tv_usec)); #endif /* PATH_TRACING_FD */ } /* * Huge sets, nfds equals to INT_MAX. */ FD_SET(fds[0], set); FD_SET(fds[1], set); tv->tv_sec = 0; tv->tv_usec = 123; XSELECT(0, INT_MAX, (uintptr_t) set, (uintptr_t) &set[1], (uintptr_t) &set[2], a_tv); #ifndef PATH_TRACING_FD printf("%s(%d, [%d %d], [], [], {tv_sec=0, tv_usec=123})" " = 0 (Timeout)\n", TEST_SYSCALL_STR, INT_MAX, fds[0], fds[1]); #else FD_SET(fds[0], set); FD_SET(fds[1], set); FD_SET(PATH_TRACING_FD, set); tv->tv_sec = 0; tv->tv_usec = 123; XSELECT(1, INT_MAX, (uintptr_t) set, (uintptr_t) &set[1], (uintptr_t) &set[2], a_tv); printf("%s(%d, [%d %d %d], [], [], {tv_sec=0, tv_usec=123})" " = 1 (in [%d], left {tv_sec=0, tv_usec=%llu})\n", TEST_SYSCALL_STR, INT_MAX, fds[0], fds[1], PATH_TRACING_FD, PATH_TRACING_FD, zero_extend_signed_to_ull(tv->tv_usec)); #endif /* PATH_TRACING_FD */ /* * Small sets, nfds exceeds FD_SETSIZE limit. * The kernel seems to be fine with it but strace cannot follow. */ *l_rs = (1UL << fds[0]) | (1UL << fds[1]) #ifdef PATH_TRACING_FD | (1UL << PATH_TRACING_FD) #endif ; *l_ws = (1UL << fds[0]); *l_es = (1UL << fds[0]) | (1UL << fds[1]) #ifdef PATH_TRACING_FD | (1UL << PATH_TRACING_FD) #endif ; tv->tv_sec = 0; tv->tv_usec = 123; rc = xselect(FD_SETSIZE + 1, a_rs, a_ws, a_es, a_tv); if (rc < 0) { #ifndef PATH_TRACING_FD printf("%s(%d, %p, %p, %p, {tv_sec=0, tv_usec=123}) = %s\n", TEST_SYSCALL_STR, FD_SETSIZE + 1, rs, ws, es, errstr); #endif } else { #ifndef PATH_TRACING_FD printf("%s(%d, %p, %p, %p, {tv_sec=0, tv_usec=123})" " = 0 (Timeout)\n", TEST_SYSCALL_STR, FD_SETSIZE + 1, rs, ws, es); #endif } /* * Small sets, one of allocated descriptors exceeds smallset_size. */ if (dup2(fds[1], smallset_size) != smallset_size) perror_msg_and_fail("dup2"); #ifdef PATH_TRACING_FD FD_SET(PATH_TRACING_FD, rs); FD_SET(PATH_TRACING_FD, ws); FD_SET(PATH_TRACING_FD, es); #endif XSELECT(-1, smallset_size + 1, a_rs, a_ws, a_es, 0); #ifndef PATH_TRACING_FD printf("%s(%d, %p, %p, %p, NULL) = %s\n", TEST_SYSCALL_STR, smallset_size + 1, rs, ws, es, errstr); #endif /* * Small and big sets, * one of allocated descriptors exceeds smallset_size. */ memset(big_rs, 0, big_size); FD_SET(fds[0], big_rs); FD_SET(smallset_size, big_rs); memset(big_ws, 0, big_size); FD_SET(fds[1], big_ws); FD_SET(smallset_size, big_ws); XSELECT(-1, smallset_size + 1, a_big_rs, a_big_ws, a_es, 0); #ifndef PATH_TRACING_FD printf("%s(%d, [%d %d], [%d %d], %p, NULL) = %s\n", TEST_SYSCALL_STR, smallset_size + 1, fds[0], smallset_size, fds[1], smallset_size, es, errstr); #endif /* !PATH_TRACING_FD */ XSELECT(-1, smallset_size + 1, a_es, a_big_ws, a_big_rs, 0); #ifndef PATH_TRACING_FD printf("%s(%d, %p, [%d %d], [%d %d], NULL) = %s\n", TEST_SYSCALL_STR, smallset_size + 1, es, fds[1], smallset_size, fds[0], smallset_size, errstr); #endif /* !PATH_TRACING_FD */ puts("+++ exited with 0 +++"); return 0; }