diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41f8e6f2a1..522df865bf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -293,6 +293,10 @@ jobs: git config --global --add safe.directory $GITHUB_WORKSPACE - name: Build and test ๐Ÿ—๏ธ๐Ÿงช + env: + # This avoids random failures on CI. + # (https://github.com/google/sanitizers/issues/1322#issuecomment-699946942) + ASAN_OPTIONS: intercept_tls_get_addr=0 run: | mkdir -p build && cd build cmake -DBUILD_BPF=ON \ @@ -302,8 +306,8 @@ jobs: -DUSE_BUNDLED_LIBBPF=ON \ .. make -j$(nproc) sinsp-example driver bpf - sudo make e2e-install-deps - sudo ../test/e2e/scripts/run_tests.sh + sudo -E make e2e-install-deps + sudo -E ../test/e2e/scripts/run_tests.sh - name: Archive test reports uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 diff --git a/.github/workflows/e2e_ci.yml b/.github/workflows/e2e_ci.yml index 69d89fb169..b8ebd154a3 100644 --- a/.github/workflows/e2e_ci.yml +++ b/.github/workflows/e2e_ci.yml @@ -74,7 +74,7 @@ jobs: sudo apt install -y --no-install-recommends linux-headers-$(uname -r) gcc-multilib g++-multilib - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.4 - name: Build e2e tests ๐Ÿ—๏ธ env: @@ -96,10 +96,11 @@ jobs: -DUSE_BUNDLED_GTEST=ON \ .. make -j6 libsinsp_e2e_tests + sudo rm -vfr test/libsinsp_e2e/resources/_proc cd .. - name: Cache build - uses: actions/cache/save@v3 + uses: actions/cache/save@v4.0.2 if: always() id: cache with: @@ -123,7 +124,7 @@ jobs: - name: Restore build id: cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4.0.2 with: path: build key: build-e2e-${{ matrix.arch }}-${{ github.run_id }} @@ -146,7 +147,7 @@ jobs: - name: Install deps run: | - sudo apt install -y --no-install-recommends clang gcc llvm build-essential cmake + sudo apt install -y --no-install-recommends clang gcc llvm build-essential cmake python2 - name: Install kernel headers (actuated) uses: self-actuated/get-kernel-sources@201eed7d915ac0a6021fb402cde5be7a6b945b59 @@ -161,9 +162,12 @@ jobs: # different workers, so we rebuild the drivers. - name: Rebuild drivers run: | - cd build + pushd build make -B driver bpf - cd .. + pushd test/libsinsp_e2e/resources/ + sudo tar xzf fake-proc.tar.gz + popd + popd - name: Run e2e tests with ${{ matrix.driver.name }} ๐ŸŽ๏ธ if: matrix.arch == 'amd64' diff --git a/driver/SCHEMA_VERSION b/driver/SCHEMA_VERSION index 7329e21c3b..4e2200b98e 100644 --- a/driver/SCHEMA_VERSION +++ b/driver/SCHEMA_VERSION @@ -1 +1 @@ -2.20.0 +2.20.1 diff --git a/driver/bpf/fillers.h b/driver/bpf/fillers.h index 7803aea520..85d2c87de0 100644 --- a/driver/bpf/fillers.h +++ b/driver/bpf/fillers.h @@ -33,23 +33,25 @@ or GPL2.txt for full copies of the license. * structures. But the syscalls (might) still use them. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) -#include -struct compat_timespec { - int32_t tv_sec; - int32_t tv_nsec; -}; - -struct timespec { - int32_t tv_sec; - int32_t tv_nsec; -}; - -struct timeval { - int32_t tv_sec; - int32_t tv_usec; -}; +#include + struct timespec { + int32_t tv_sec; + int32_t tv_nsec; + }; + + struct timeval { + int32_t tv_sec; + int32_t tv_usec; + }; + typedef struct old_timespec32 old_timespec32; #else -#define timeval64 timeval + #if __has_include() + #include + #else + #include + #endif + #define timeval64 timeval + typedef struct compat_timespec old_timespec32; #endif #define FILLER_RAW(x) \ @@ -570,17 +572,18 @@ FILLER(sys_poll_x, true) } #define MAX_IOVCNT 32 +#define MAX_IOVCNT_COMPAT 8 -static __always_inline int bpf_parse_readv_writev_bufs(struct filler_data *data, - const struct iovec __user *iovsrc, +static __always_inline int bpf_parse_readv_writev_bufs_64(struct filler_data *data, + const void __user *iovsrc, unsigned long iovcnt, long retval, - int flags) + int flags, + unsigned long *size) { const struct iovec *iov; int res = PPM_SUCCESS; unsigned long copylen; - long size = 0; int j; copylen = iovcnt * sizeof(struct iovec); @@ -592,42 +595,41 @@ static __always_inline int bpf_parse_readv_writev_bufs(struct filler_data *data, } #ifdef BPF_FORBIDS_ZERO_ACCESS - if (copylen) + if (copylen) + if (bpf_probe_read_user((void *)iov, + ((copylen - 1) & SCRATCH_SIZE_MAX) + 1, + (void *)iovsrc)) +#else if (bpf_probe_read_user((void *)iov, - ((copylen - 1) & SCRATCH_SIZE_MAX) + 1, + copylen & SCRATCH_SIZE_MAX, (void *)iovsrc)) -#else - if (bpf_probe_read_user((void *)iov, - copylen & SCRATCH_SIZE_MAX, - (void *)iovsrc)) #endif - return PPM_FAILURE_INVALID_USER_MEMORY; - + return PPM_FAILURE_INVALID_USER_MEMORY; #pragma unroll for (j = 0; j < MAX_IOVCNT; ++j) { if (j == iovcnt) break; // BPF seems to require a hard limit to avoid overflows - if (size == LONG_MAX) + if (*size == LONG_MAX) break; - size += iov[j].iov_len; + *size += iov[j].iov_len; } if ((flags & PRB_FLAG_IS_WRITE) == 0) - if (size > retval) - size = retval; + if (*size > retval) + *size = retval; - if (flags & PRB_FLAG_PUSH_SIZE) { - res = bpf_push_u32_to_ring(data, (uint32_t)size); + if (flags & PRB_FLAG_PUSH_SIZE && res == PPM_SUCCESS) { + res = bpf_push_u32_to_ring(data, (uint32_t)*size); CHECK_RES(res); } if (flags & PRB_FLAG_PUSH_DATA) { - if (size > 0) { + if (*size > 0) { unsigned long off = _READ(data->state->tail_ctx.curoff); - unsigned long remaining = size; + unsigned long remaining = *size; int j; #pragma unroll @@ -649,30 +651,160 @@ static __always_inline int bpf_parse_readv_writev_bufs(struct filler_data *data, if (to_read > SCRATCH_SIZE_HALF) to_read = SCRATCH_SIZE_HALF; -#ifdef BPF_FORBIDS_ZERO_ACCESS - if (to_read) + #ifdef BPF_FORBIDS_ZERO_ACCESS + if (to_read) + if (bpf_probe_read_user(&data->buf[off_bounded], + ((to_read - 1) & SCRATCH_SIZE_HALF) + 1, + (void*)iov[j].iov_base)) + #else if (bpf_probe_read_user(&data->buf[off_bounded], - ((to_read - 1) & SCRATCH_SIZE_HALF) + 1, - iov[j].iov_base)) + to_read & SCRATCH_SIZE_HALF, + (void*)iov[j].iov_base)) + #endif + return PPM_FAILURE_INVALID_USER_MEMORY; + + remaining -= to_read; + off += to_read; + } + } else { + *size = 0; + } + return PPM_SUCCESS; + } + + return res; +} + +#ifdef CONFIG_COMPAT +static __always_inline int bpf_parse_readv_writev_bufs_32(struct filler_data *data, + const void __user *iovsrc, + unsigned long iovcnt, + long retval, + int flags, + unsigned long *size) +{ + const struct compat_iovec *compat_iov; + int res = PPM_SUCCESS; + unsigned long copylen; + int j; + + copylen = iovcnt * sizeof(struct compat_iovec); + compat_iov = (const struct compat_iovec *)data->tmp_scratch; + + if (copylen > SCRATCH_SIZE_MAX) + { + return PPM_FAILURE_FRAME_SCRATCH_MAP_FULL; + } + +#ifdef BPF_FORBIDS_ZERO_ACCESS + if (copylen) + if (bpf_probe_read_user((void *)compat_iov, + ((copylen - 1) & SCRATCH_SIZE_MAX) + 1, + (void *)iovsrc)) #else - if (bpf_probe_read_user(&data->buf[off_bounded], - to_read & SCRATCH_SIZE_HALF, - iov[j].iov_base)) + if (bpf_probe_read_user((void *)compat_iov, + copylen & SCRATCH_SIZE_MAX, + (void *)iovsrc)) #endif - return PPM_FAILURE_INVALID_USER_MEMORY; + return PPM_FAILURE_INVALID_USER_MEMORY; + + #pragma unroll + for (j = 0; j < MAX_IOVCNT_COMPAT; ++j) { + if (j == iovcnt) + break; + // BPF seems to require a hard limit to avoid overflows + if (*size == LONG_MAX) + break; + + *size += compat_iov[j].iov_len; + } + + if ((flags & PRB_FLAG_IS_WRITE) == 0) + if (*size > retval) + *size = retval; + + if (flags & PRB_FLAG_PUSH_SIZE && res == PPM_SUCCESS) { + res = bpf_push_u32_to_ring(data, (uint32_t)*size); + CHECK_RES(res); + } + + if (flags & PRB_FLAG_PUSH_DATA) { + if (*size > 0) { + unsigned long off = _READ(data->state->tail_ctx.curoff); + unsigned long remaining = *size; + int j; + + // The 14 iovec count limit is due to old kernels verifiers + // complaining. + #pragma unroll + for (j = 0; j < MAX_IOVCNT_COMPAT; ++j) { + volatile unsigned int to_read; + + if (j == iovcnt) + break; + + unsigned long off_bounded = off & SCRATCH_SIZE_HALF; + if (off > SCRATCH_SIZE_HALF) + break; + + if (compat_iov[j].iov_len <= remaining) + to_read = compat_iov[j].iov_len; + else + to_read = remaining; + + if (to_read > SCRATCH_SIZE_HALF) + to_read = SCRATCH_SIZE_HALF; + + #ifdef BPF_FORBIDS_ZERO_ACCESS + if (to_read) + if (bpf_probe_read_user(&data->buf[off_bounded], + ((to_read - 1) & SCRATCH_SIZE_HALF) + 1, + (void*)compat_iov[j].iov_base)) + #else + if (bpf_probe_read_user(&data->buf[off_bounded], + to_read & SCRATCH_SIZE_HALF, + (void*)compat_iov[j].iov_base)) + #endif + return PPM_FAILURE_INVALID_USER_MEMORY; remaining -= to_read; off += to_read; } } else { - size = 0; + *size = 0; } + return PPM_SUCCESS; + } + return res; +} +#endif + +static __always_inline int bpf_parse_readv_writev_bufs(struct filler_data *data, + const void __user *iovsrc, + unsigned long iovcnt, + long retval, + int flags) +{ + unsigned long size = 0; + int res = PPM_SUCCESS; + if (!bpf_in_ia32_syscall()) + { + res = bpf_parse_readv_writev_bufs_64(data, iovsrc, iovcnt, retval, flags, &size); + } + else + { +#ifdef CONFIG_COMPAT + res = bpf_parse_readv_writev_bufs_32(data, iovsrc, iovcnt, retval, flags, &size); +#endif + } + + if(flags & PRB_FLAG_PUSH_DATA && res == PPM_SUCCESS) + { data->fd = bpf_syscall_get_argument(data, 0); data->curarg_already_on_frame = true; return __bpf_val_to_ring(data, 0, size, PT_BYTEBUF, -1, true, KERNEL); } - return res; } @@ -790,10 +922,10 @@ FILLER(sys_writev_pwritev_x, true) val = bpf_syscall_get_argument(data, 1); iovcnt = bpf_syscall_get_argument(data, 2); res = bpf_parse_readv_writev_bufs(data, - (const struct iovec __user *)val, - iovcnt, - 0, - PRB_FLAG_PUSH_DATA | PRB_FLAG_IS_WRITE); + (const struct iovec __user *)val, + iovcnt, + 0, + PRB_FLAG_PUSH_DATA | PRB_FLAG_IS_WRITE); /* if there was an error we send an empty param. * we can improve this in the future but at least we don't lose the whole event. @@ -809,13 +941,22 @@ FILLER(sys_writev_pwritev_x, true) static __always_inline int timespec_parse(struct filler_data *data, unsigned long val) { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) - struct __kernel_timespec ts = {}; -#else - struct timespec ts = {}; -#endif - bpf_probe_read_user(&ts, sizeof(ts), (void *)val); - return bpf_push_u64_to_ring(data, ((uint64_t)ts.tv_sec) * 1000000000 + ts.tv_nsec); + if (!bpf_in_ia32_syscall()) + { + #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) + struct __kernel_timespec ts = {}; + #else + struct timespec ts = {}; + #endif + bpf_probe_read_user(&ts, sizeof(ts), (void *)val); + return bpf_push_u64_to_ring(data, ((uint64_t)ts.tv_sec) * 1000000000 + ts.tv_nsec); + } + else + { + old_timespec32 ts = {}; + bpf_probe_read_user(&ts, sizeof(ts), (void *)val); + return bpf_push_u64_to_ring(data, ((uint32_t)ts.tv_sec) * 1000000000 + ts.tv_nsec); + } } FILLER(sys_nanosleep_e, true) @@ -3071,12 +3212,10 @@ FILLER(sys_generic, true) // if we are in ia32 syscall sys_{enter,exit} already // validated the converted 32bit->64bit syscall ID for us, // otherwise the event would've been discarded. -#if defined(CONFIG_X86_64) && defined(CONFIG_IA32_EMULATION) if (bpf_in_ia32_syscall()) { native_id = convert_ia32_to_64(native_id); } -#endif sc_evt = get_syscall_info(native_id); if (!sc_evt) { @@ -3993,10 +4132,10 @@ FILLER(sys_pwritev_e, true) /* Parameter 2: size (type: PT_UINT32) */ res = bpf_parse_readv_writev_bufs(data, - (const struct iovec __user *)iov_pointer, - iov_cnt, - 0, - PRB_FLAG_PUSH_SIZE | PRB_FLAG_IS_WRITE); + (const struct iovec __user *)iov_pointer, + iov_cnt, + 0, + PRB_FLAG_PUSH_SIZE | PRB_FLAG_IS_WRITE); /* if there was an error we send a size equal to `0`. * we can improve this in the future but at least we don't lose the whole event. diff --git a/driver/modern_bpf/definitions/struct_flavors.h b/driver/modern_bpf/definitions/struct_flavors.h index 341a2b0597..677a98c630 100644 --- a/driver/modern_bpf/definitions/struct_flavors.h +++ b/driver/modern_bpf/definitions/struct_flavors.h @@ -79,4 +79,15 @@ struct modern_bpf__kernel_timex_timeval long long int tv_usec; }; +/* + * This is equivalent to old_timespec32 or compat_timespec. Some old distros + * don't define old_timespec32 (e.g. centos 8 with 4.18 kernel), so we define + * it here. + */ +struct modern_bpf__kernel_timespec_ia32 +{ + int tv_sec; + int tv_nsec; +}; + #endif /* __STRUCT_FLAVORS_H__ */ diff --git a/driver/modern_bpf/helpers/store/auxmap_store_params.h b/driver/modern_bpf/helpers/store/auxmap_store_params.h index 60f13572b6..840247ec93 100644 --- a/driver/modern_bpf/helpers/store/auxmap_store_params.h +++ b/driver/modern_bpf/helpers/store/auxmap_store_params.h @@ -1003,10 +1003,11 @@ static __always_inline void auxmap__store_iovec_size_param(struct auxiliary_map * @param iov_cnt number of `iovec` structs to be read from userspace. * @param len_to_read imposed snaplen. */ -static __always_inline void auxmap__store_iovec_data_param(struct auxiliary_map *auxmap, unsigned long iov_pointer, unsigned long iov_cnt, unsigned long len_to_read) +static __always_inline void auxmap__store_iovec_data_param_64(struct auxiliary_map *auxmap, unsigned long iov_pointer, unsigned long iov_cnt, unsigned long len_to_read) { /* We use the second part of our auxmap as a scratch space. */ - uint32_t total_iovec_size = iov_cnt * bpf_core_type_size(struct iovec); + unsigned long total_iovec_size = iov_cnt * bpf_core_type_size(struct iovec); + if(bpf_probe_read_user((void *)&auxmap->data[MAX_PARAM_SIZE], SAFE_ACCESS(total_iovec_size), (void *)iov_pointer)) @@ -1016,7 +1017,7 @@ static __always_inline void auxmap__store_iovec_data_param(struct auxiliary_map return; } - uint32_t total_size_to_read = 0; + unsigned long total_size_to_read = 0; /* Pointer to iovec structs */ const struct iovec *iovec = (const struct iovec *)&auxmap->data[MAX_PARAM_SIZE]; @@ -1050,6 +1051,66 @@ static __always_inline void auxmap__store_iovec_data_param(struct auxiliary_map push__param_len(auxmap->data, &auxmap->lengths_pos, total_size_to_read); } +static __always_inline void auxmap__store_iovec_data_param_32(struct auxiliary_map *auxmap, unsigned long iov_pointer, unsigned long iov_cnt, unsigned long len_to_read) +{ + /* We use the second part of our auxmap as a scratch space. */ + unsigned long total_iovec_size = iov_cnt * bpf_core_type_size(struct compat_iovec); + + if(bpf_probe_read_user((void *)&auxmap->data[MAX_PARAM_SIZE], + SAFE_ACCESS(total_iovec_size), + (void *)iov_pointer)) + { + /* in case of NULL iovec vector we return an empty param */ + push__param_len(auxmap->data, &auxmap->lengths_pos, 0); + return; + } + + unsigned long total_size_to_read = 0; + + /* Pointer to iovec structs */ + const struct compat_iovec *compat_iovec = (const struct compat_iovec *)&auxmap->data[MAX_PARAM_SIZE]; + uint64_t initial_payload_pos = auxmap->payload_pos; + for(int j = 0; j < MAX_IOVCNT; j++) + { + if(total_size_to_read > len_to_read) + { + /* If we break here it could be that `payload_pos` overcame the max `len_to_read` for this reason + * we have an enforcement after the for loop. + */ + total_size_to_read = len_to_read; + break; + } + + if(j == iov_cnt) + { + break; + } + + uint16_t bytes_read = push__bytebuf(auxmap->data, &auxmap->payload_pos, (unsigned long)compat_iovec[j].iov_base, compat_iovec[j].iov_len, USER); + if(!bytes_read) + { + push__param_len(auxmap->data, &auxmap->lengths_pos, total_size_to_read); + return; + } + total_size_to_read += bytes_read; + } + /* We need this enforcement to be sure that we don't overcome the max `len_to_read` */ + auxmap->payload_pos = initial_payload_pos + total_size_to_read; + push__param_len(auxmap->data, &auxmap->lengths_pos, total_size_to_read); +} + +static __always_inline void auxmap__store_iovec_data_param(struct auxiliary_map *auxmap, unsigned long iov_pointer, unsigned long iov_cnt, unsigned long len_to_read) +{ + if(!bpf_in_ia32_syscall()) + { + auxmap__store_iovec_data_param_64(auxmap, iov_pointer, iov_cnt, len_to_read); + } + else + { + auxmap__store_iovec_data_param_32(auxmap, iov_pointer, iov_cnt, len_to_read); + } +} + /** * @brief Store the size extracted from a `user_msghdr` struct. * Please note: the size is an unsigned 32 bit value so diff --git a/driver/modern_bpf/helpers/store/ringbuf_store_params.h b/driver/modern_bpf/helpers/store/ringbuf_store_params.h index 722331ef9b..7f3f61c6d1 100644 --- a/driver/modern_bpf/helpers/store/ringbuf_store_params.h +++ b/driver/modern_bpf/helpers/store/ringbuf_store_params.h @@ -319,7 +319,16 @@ static __always_inline void ringbuf__store_iovec_size_param(struct ringbuf_struc return; } - uint32_t total_iovec_size = iov_cnt * bpf_core_type_size(struct iovec); + uint32_t total_iovec_size = 0; + if(!bpf_in_ia32_syscall()) + { + total_iovec_size = iov_cnt * bpf_core_type_size(struct iovec); + } + else + { + total_iovec_size = iov_cnt * bpf_core_type_size(struct compat_iovec); + } + if(bpf_probe_read_user((void *)&auxmap->data[0], SAFE_ACCESS(total_iovec_size), (void *)iov_pointer)) @@ -331,14 +340,29 @@ static __always_inline void ringbuf__store_iovec_size_param(struct ringbuf_struc uint32_t total_size_to_read = 0; /* Pointer to iovec structs */ - const struct iovec *iovec = (const struct iovec *)&auxmap->data[0]; - for(int j = 0; j < MAX_IOVCNT; j++) + if(!bpf_in_ia32_syscall()) + { + const struct iovec *iovec = (const struct iovec *)&auxmap->data[0]; + for(int j = 0; j < MAX_IOVCNT; j++) + { + if(j == iov_cnt) + { + break; + } + total_size_to_read += iovec[j].iov_len; + } + } + else { - if(j == iov_cnt) + const struct compat_iovec *iovec = (const struct compat_iovec *)&auxmap->data[0]; + for(int j = 0; j < MAX_IOVCNT; j++) { - break; + if(j == iov_cnt) + { + break; + } + total_size_to_read += iovec[j].iov_len; } - total_size_to_read += iovec[j].iov_len; } ringbuf__store_u32(ringbuf, total_size_to_read); } diff --git a/driver/modern_bpf/programs/tail_called/events/syscall_dispatched_events/ppoll.bpf.c b/driver/modern_bpf/programs/tail_called/events/syscall_dispatched_events/ppoll.bpf.c index 84ec7f619d..12359d568b 100644 --- a/driver/modern_bpf/programs/tail_called/events/syscall_dispatched_events/ppoll.bpf.c +++ b/driver/modern_bpf/programs/tail_called/events/syscall_dispatched_events/ppoll.bpf.c @@ -38,17 +38,26 @@ int BPF_PROG(ppoll_e, /* Parameter 2: timeout (type: PT_RELTIME) */ uint64_t nanosec = 0; unsigned long ts_pointer = extract__syscall_argument(regs, 2); - if(bpf_core_type_exists(struct __kernel_timespec)) + if(!bpf_in_ia32_syscall()) { - struct __kernel_timespec ts = {0}; - bpf_probe_read_user(&ts, bpf_core_type_size(struct __kernel_timespec), (void *)ts_pointer); - nanosec = ((uint64_t)ts.tv_sec) * SECOND_TO_NS + ts.tv_nsec; + if(bpf_core_type_exists(struct __kernel_timespec)) + { + struct __kernel_timespec ts = {0}; + bpf_probe_read_user(&ts, bpf_core_type_size(struct __kernel_timespec), (void *)ts_pointer); + nanosec = ((uint64_t)ts.tv_sec) * SECOND_TO_NS + ts.tv_nsec; + } + else + { + struct modern_bpf__kernel_timespec ts = {0}; + bpf_probe_read_user(&ts, sizeof(ts), (void *)ts_pointer); + nanosec = ((uint64_t)ts.tv_sec) * SECOND_TO_NS + ts.tv_nsec; + } } else { - struct modern_bpf__kernel_timespec ts = {0}; + struct modern_bpf__kernel_timespec_ia32 ts = {0}; bpf_probe_read_user(&ts, sizeof(ts), (void *)ts_pointer); - nanosec = ((uint64_t)ts.tv_sec) * SECOND_TO_NS + ts.tv_nsec; + nanosec = ((uint32_t)ts.tv_sec) * SECOND_TO_NS + ts.tv_nsec; } auxmap__store_u64_param(auxmap, nanosec); diff --git a/test/e2e/tests/requirements.txt b/test/e2e/tests/requirements.txt index e5517c95df..8a7b114d63 100644 --- a/test/e2e/tests/requirements.txt +++ b/test/e2e/tests/requirements.txt @@ -1,3 +1,4 @@ pytest==7.1.1 pytest-html==3.1.1 docker==6.0.0 +requests==2.31.0 diff --git a/test/libsinsp_e2e/CMakeLists.txt b/test/libsinsp_e2e/CMakeLists.txt index 22b44f096b..2f800f8d74 100755 --- a/test/libsinsp_e2e/CMakeLists.txt +++ b/test/libsinsp_e2e/CMakeLists.txt @@ -32,6 +32,7 @@ configure_file ( ) add_executable(libsinsp_e2e_tests + capture_to_file_test.cpp container/container.cpp container/container_cgroup.cpp container/docker_utils.cpp @@ -50,6 +51,7 @@ add_executable(libsinsp_e2e_tests threadinfo.cpp thread_state.cpp udp_client_server.cpp + unix_client_server.cpp ) if(BUILD_BPF) @@ -103,3 +105,8 @@ file(COPY DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/resources/ ) + +execute_process( + COMMAND tar xzf ${CMAKE_CURRENT_BINARY_DIR}/resources/fake-proc.tar.gz + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/resources/ + ) diff --git a/test/libsinsp_e2e/capture_to_file_test.cpp b/test/libsinsp_e2e/capture_to_file_test.cpp new file mode 100644 index 0000000000..9302b55948 --- /dev/null +++ b/test/libsinsp_e2e/capture_to_file_test.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include "sys_call_test.h" + +#include +#include + +#include + +TEST_F(sys_call_test, can_consume_a_capture_file) +{ + int callnum = 0; + + event_filter_t filter = [&](sinsp_evt* evt) + { + std::string evt_name(evt->get_name()); + return evt_name.find("stat") != std::string::npos && + m_tid_filter(evt) && evt->get_direction() == SCAP_ED_OUT; + }; + + run_callback_t test = [](concurrent_object_handle inspector_handle) + { + struct stat sb; + for(int i = 0; i < 100; i++) + { + stat("/tmp", &sb); + } + }; + + captured_event_callback_t callback = [&](const callback_param& param) { callnum++; }; + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(100, callnum); + + sinsp inspector; + sinsp_evt* event; + + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + auto filename = std::string(LIBSINSP_TEST_CAPTURES_PATH) + test_info->test_case_name() + "_" + + test_info->name() + ".scap"; + inspector.open_savefile(filename); + callnum = 0; + int32_t res; + while((res = inspector.next(&event)) != SCAP_EOF) + { + ASSERT_EQ(SCAP_SUCCESS, res); + std::string evt_name(event->get_name()); + if(evt_name.find("stat") != std::string::npos && + m_tid_filter(event) && event->get_direction() == SCAP_ED_OUT) + { + callnum++; + } + } + + ASSERT_EQ(SCAP_EOF, res); + ASSERT_EQ(100, callnum); +} diff --git a/test/libsinsp_e2e/container/container.cpp b/test/libsinsp_e2e/container/container.cpp index 8fbe783fc1..78d2cd699c 100644 --- a/test/libsinsp_e2e/container/container.cpp +++ b/test/libsinsp_e2e/container/container.cpp @@ -446,7 +446,7 @@ TEST_F(sys_call_test, container_libvirt) if (system("virsh --help > /dev/null 2>&1") != 0) { - printf("libvirt not installed, skipping test\n"); + GTEST_SKIP() << "libvirt not installed, skipping test"; return; } @@ -722,6 +722,83 @@ static void healthcheck_helper( ASSERT_EQ(cstate.healthcheck_seen, expect_healthcheck) << capture_stats_str; } +static void healthcheck_tracefile_helper( + const std::string& dockerfile, + bool expect_healthcheck, + sinsp_threadinfo::command_category expected_cat = sinsp_threadinfo::CAT_HEALTHCHECK) +{ + container_state cstate; + + std::string build_cmdline("cd " LIBSINSP_TEST_RESOURCES_PATH "/docker/health_dockerfiles && docker build -t cont_health_ut_img -f " + + dockerfile + " . > /dev/null 2>&1"); + ASSERT_TRUE(system(build_cmdline.c_str()) == 0); + + run_callback_t test = [](concurrent_object_handle inspector_handle) + { + // --network=none speeds up the container setup a bit. + ASSERT_TRUE((system("docker run --rm --network=none --name cont_health_ut cont_health_ut_img " + "/bin/sh -c '/bin/sleep 10' > /dev/null 2>&1")) == 0); + }; + + event_filter_t filter = [&](sinsp_evt* evt) + { + std::string evt_name(evt->get_name()); + return evt_name.find("execve") != std::string::npos && + evt->get_direction() == SCAP_ED_OUT; + }; + + captured_event_callback_t callback = [&](const callback_param& param) {return;}; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + + // Now reread the file we just wrote and pass it through + // update_container_state. + + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + auto dumpfile = std::string(LIBSINSP_TEST_CAPTURES_PATH) + test_info->test_case_name() + "_" + + test_info->name() + ".scap"; + + sinsp inspector; + inspector.set_hostname_and_port_resolution_mode(false); + inspector.set_filter("evt.type=execve and evt.dir=<"); + inspector.open_savefile(dumpfile); + inspector.start_capture(); + + while (1) + { + sinsp_evt* ev; + int32_t res = inspector.next(&ev); + + if (res == SCAP_TIMEOUT) + { + continue; + } + if (res == SCAP_FILTERED_EVENT) + { + continue; + } + else if (res == SCAP_EOF) + { + break; + } + ASSERT_TRUE(res == SCAP_SUCCESS); + + update_container_state(&inspector, ev, cstate, expected_cat); + } + + std::string capture_stats_str = capture_stats(&inspector); + + inspector.stop_capture(); + inspector.close(); + + ASSERT_TRUE(cstate.root_cmd_seen) << capture_stats_str; + ASSERT_TRUE(cstate.second_cmd_seen) << capture_stats_str; + ASSERT_EQ(cstate.container_w_health_probe, expect_healthcheck) << capture_stats_str; + ASSERT_EQ(cstate.healthcheck_seen, expect_healthcheck) << capture_stats_str; +} + + // Run container w/o health check, should not find any health check // for the container. Should not identify either the entrypoint // or a second process spawned after as a health check process. @@ -791,6 +868,33 @@ TEST_F(sys_call_test, docker_container_readiness_probe) sinsp_threadinfo::CAT_READINESS_PROBE); } +// Identical to above tests, but read events from a trace file instead +// of live. Only doing selected cases. +TEST_F(sys_call_test, docker_container_healthcheck_trace) +{ + healthcheck_tracefile_helper("Dockerfile.healthcheck", true); +} + +TEST_F(sys_call_test, docker_container_healthcheck_cmd_overlap_trace) +{ + healthcheck_tracefile_helper("Dockerfile.healthcheck_cmd_overlap", true); +} + +TEST_F(sys_call_test, docker_container_liveness_probe_trace) +{ + healthcheck_tracefile_helper("Dockerfile.healthcheck_liveness", + true, + sinsp_threadinfo::CAT_LIVENESS_PROBE); +} + +TEST_F(sys_call_test, docker_container_readiness_probe_trace) +{ + healthcheck_tracefile_helper("Dockerfile.healthcheck_readiness", + true, + sinsp_threadinfo::CAT_READINESS_PROBE); +} + + TEST_F(sys_call_test, docker_container_large_json) { bool saw_container_evt = false; diff --git a/test/libsinsp_e2e/event_capture.cpp b/test/libsinsp_e2e/event_capture.cpp index 98782e991f..9059db35db 100644 --- a/test/libsinsp_e2e/event_capture.cpp +++ b/test/libsinsp_e2e/event_capture.cpp @@ -125,7 +125,7 @@ void event_capture::capture() if (SCAP_SUCCESS == next_result) { result = handle_event(event); - if (m_mode != SINSP_MODE_NODRIVER) + if (m_mode != SINSP_MODE_NODRIVER && m_dump) { dumper->dump(event); } @@ -169,7 +169,10 @@ void event_capture::capture() { break; } - dumper->dump(event); + if(m_dump) + { + dumper->dump(event); + } result = handle_event(event); } { @@ -177,7 +180,6 @@ void event_capture::capture() while (SCAP_SUCCESS == get_inspector()->next(&event)) { // just consume the remaining events - dumper->dump(event); } } } diff --git a/test/libsinsp_e2e/event_capture.h b/test/libsinsp_e2e/event_capture.h index b1d3c2cdf1..2a8a369479 100644 --- a/test/libsinsp_e2e/event_capture.h +++ b/test/libsinsp_e2e/event_capture.h @@ -147,7 +147,8 @@ class event_capture uint64_t thread_timeout_ns = (uint64_t)60 * 1000 * 1000 * 1000, uint64_t inactive_thread_scan_time_ns = (uint64_t)60 * 1000 * 1000 * 1000, sinsp_mode_t mode = SINSP_MODE_LIVE, - uint64_t max_timeouts = 3) + uint64_t max_timeouts = 3, + bool dump = true) { event_capture capturing; { // Synchronized section @@ -162,6 +163,7 @@ class event_capture capturing.m_thread_timeout_ns = thread_timeout_ns; capturing.m_inactive_thread_scan_time_ns = inactive_thread_scan_time_ns; capturing.m_max_timeouts = max_timeouts; + capturing.m_dump = dump; } @@ -233,4 +235,5 @@ class event_capture static bool inspector_ok; sinsp_mode_t m_mode; uint64_t m_max_timeouts; + bool m_dump; }; diff --git a/test/libsinsp_e2e/fs.cpp b/test/libsinsp_e2e/fs.cpp index fd1fe67d57..b2d9b83865 100644 --- a/test/libsinsp_e2e/fs.cpp +++ b/test/libsinsp_e2e/fs.cpp @@ -1465,7 +1465,11 @@ TEST_F(sys_call_test, large_read_write) inspector->set_snaplen(DEFAULT_SNAPLEN); }; - ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter, setup, cleanup); }); + // We don't dump events to scap files, otherwise we could stuck with modern bpf. + ASSERT_NO_FATAL_FAILURE({event_capture::run(test, callback, filter, setup, + cleanup, event_capture::always_continue, 131072, + (uint64_t)60 * 1000 * 1000 * 1000, (uint64_t)60 * 1000 * 1000 * 1000, + SINSP_MODE_LIVE, 3, false); }); EXPECT_EQ(4, callnum); } @@ -1603,7 +1607,11 @@ TEST_F(sys_call_test, large_readv_writev) inspector->set_snaplen(DEFAULT_SNAPLEN); }; - ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter, setup, cleanup); }); + // We don't dump events to scap files, otherwise we could stuck with modern bpf. + ASSERT_NO_FATAL_FAILURE({event_capture::run(test, callback, filter, setup, + cleanup, event_capture::always_continue, 131072, + (uint64_t)60 * 1000 * 1000 * 1000, (uint64_t)60 * 1000 * 1000 * 1000, + SINSP_MODE_LIVE, 3, false); }); EXPECT_EQ(4, callnum); } diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/CMakeLists.txt b/test/libsinsp_e2e/resources/docker/health_dockerfiles/CMakeLists.txt new file mode 100644 index 0000000000..0ea56df7da --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/CMakeLists.txt @@ -0,0 +1,17 @@ +foreach( + dockerfile + Dockerfile.healthcheck + Dockerfile.healthcheck_shell + Dockerfile.healthcheck_cmd_overlap + Dockerfile.healthcheck_liveness + Dockerfile.healthcheck_readiness + Dockerfile.no_healthcheck + Dockerfile.none_healthcheck +) + + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/${dockerfile} ${CMAKE_CURRENT_BINARY_DIR}/${dockerfile} + COPYONLY + ) + +endforeach(dockerfile) diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck new file mode 100644 index 0000000000..b140f006c7 --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck @@ -0,0 +1,3 @@ +FROM busybox +RUN cp /bin/true /bin/ut-health-check +HEALTHCHECK --interval=0.5s CMD ["/bin/ut-health-check"] diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_cmd_overlap b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_cmd_overlap new file mode 100644 index 0000000000..405b4153be --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_cmd_overlap @@ -0,0 +1,2 @@ +FROM busybox +HEALTHCHECK --interval=0.5s CMD ["/bin/sh", "-c", "/bin/sleep 10"] diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_liveness b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_liveness new file mode 100644 index 0000000000..91cbf0da6d --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_liveness @@ -0,0 +1,8 @@ +FROM busybox +RUN cp /bin/true /bin/ut-health-check + +# This container runs a docker healthcheck, but due to the +# annotation.... label, it gets interpretated as if it were a k8s +# liveness check. +HEALTHCHECK --interval=0.5s CMD ["/bin/ut-health-check"] +LABEL annotation.kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"mysql-app\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"MYSQL_ROOT_PASSWORD\",\"value\":\"no\"}],\"image\":\"mstemm/mysql:healthcheck\",\"livenessProbe\":{\"exec\":{\"command\":[\"/bin/ut-health-check\"]},\"initialDelaySeconds\":5,\"periodSeconds\":5},\"name\":\"mysql\"}]}}\n" diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_readiness b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_readiness new file mode 100644 index 0000000000..5919b746bf --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_readiness @@ -0,0 +1,8 @@ +FROM busybox +RUN cp /bin/true /bin/ut-health-check + +# This container runs a docker healthcheck, but due to the +# annotation.... label, it gets interpretated as if it were a k8s +# readiness check. +HEALTHCHECK --interval=0.5s CMD ["/bin/ut-health-check"] +LABEL annotation.kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"mysql-app\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"MYSQL_ROOT_PASSWORD\",\"value\":\"no\"}],\"image\":\"mstemm/mysql:healthcheck\",\"readinessProbe\":{\"exec\":{\"command\":[\"/bin/ut-health-check\"]},\"initialDelaySeconds\":5,\"periodSeconds\":5},\"name\":\"mysql\"}]}}\n" diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_shell b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_shell new file mode 100644 index 0000000000..0c908253ab --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_shell @@ -0,0 +1,3 @@ +FROM busybox +RUN cp /bin/true /bin/ut-health-check +HEALTHCHECK --interval=0.5s CMD /bin/ut-health-check diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.no_healthcheck b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.no_healthcheck new file mode 100644 index 0000000000..9a3adf68b5 --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.no_healthcheck @@ -0,0 +1 @@ +FROM busybox:latest diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.none_healthcheck b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.none_healthcheck new file mode 100644 index 0000000000..f5a257ee6f --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.none_healthcheck @@ -0,0 +1,2 @@ +FROM busybox:latest +HEALTHCHECK NONE diff --git a/test/libsinsp_e2e/resources/fake-proc.tar.gz b/test/libsinsp_e2e/resources/fake-proc.tar.gz new file mode 100644 index 0000000000..af4c9f9b26 Binary files /dev/null and b/test/libsinsp_e2e/resources/fake-proc.tar.gz differ diff --git a/test/libsinsp_e2e/resources/unix_client_server.py b/test/libsinsp_e2e/resources/unix_client_server.py new file mode 100644 index 0000000000..f183eaac65 --- /dev/null +++ b/test/libsinsp_e2e/resources/unix_client_server.py @@ -0,0 +1,43 @@ +#!/usr/bin/python2 +# coding: utf-8 -*- +import socket +import os, os.path +import time +import sys + +PAYLOAD = "0123456789QWERTYUIOPASDFGHJKLZXCVBNM" +NAME = "/tmp/python_unix_sockets_example" + +if sys.argv[1] == 'server': + if os.path.exists(NAME): + os.remove(NAME) + + server = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM ) + server.bind(NAME) + sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) + + print "STARTED" + server.listen(5) + + connect, address = server.accept() + resp = connect.recv( 1024 ) + connect.send(resp) + connect.close() + server.close() + os.remove(NAME) + +else: + sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) + + if os.path.exists(NAME): + client = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM ) + client.connect(NAME) + + print "STARTED" + + client.send(PAYLOAD) + resp = client.recv(1024) + client.close() + + else: + print >> sys.stderr, "Couldn't Connect!" diff --git a/test/libsinsp_e2e/subprocess.cpp b/test/libsinsp_e2e/subprocess.cpp index 62d45d8f0a..4912f86827 100644 --- a/test/libsinsp_e2e/subprocess.cpp +++ b/test/libsinsp_e2e/subprocess.cpp @@ -30,11 +30,13 @@ limitations under the License. #include #include -subprocess::subprocess(std::string command, std::vector arguments, bool start_now) - : m_pid(-1) +subprocess::subprocess(std::string command, std::vector arguments, + bool start_now, int retry_attempts): + m_pid(-1), + m_retry_attemps(retry_attempts), + m_command(command), + m_args(arguments) { - m_command = command; - m_args = arguments; if(start_now) { start(); @@ -60,20 +62,26 @@ void subprocess::wait_for_start() timeout.tv_usec = 0; int result = select(m_out_pipe[0] + 1, &read_set, nullptr, nullptr, &timeout); + int attempt = 0; - switch(result) + while(attempt < m_retry_attemps) { - case -1: - perror("select"); - break; - case 0: - std::cerr << "Timeout waiting for process to start." << std::endl; - break; - default: - if (!FD_ISSET(m_out_pipe[0], &read_set)) { - std::cerr << "Unexpected error during select." << std::endl; - } - break; + switch(result) + { + case -1: + perror("select"); + break; + case 0: + std::cerr << "Timeout waiting for process to start. Retry n." + << (attempt + 1) << "/" << m_retry_attemps << std::endl; + break; + default: + if (!FD_ISSET(m_out_pipe[0], &read_set)) { + std::cerr << "Unexpected error during select." << std::endl; + } + break; + } + attempt++; } } diff --git a/test/libsinsp_e2e/subprocess.h b/test/libsinsp_e2e/subprocess.h index 2c0345e552..ef9fe01ca1 100644 --- a/test/libsinsp_e2e/subprocess.h +++ b/test/libsinsp_e2e/subprocess.h @@ -31,8 +31,8 @@ limitations under the License. class subprocess { public: - - subprocess(std::string command, std::vector arguments, bool start_now=true); + subprocess(std::string command, std::vector arguments, + bool start_now=true, int retry_attempts=3); ~subprocess(); void wait_for_start(); @@ -51,6 +51,7 @@ class subprocess { pid_t m_pid; int m_in_pipe[2]; int m_out_pipe[2]; + int m_retry_attemps; std::ostream* m_in_stream; std::istream* m_out_stream; diff --git a/test/libsinsp_e2e/sys_call_test.cpp b/test/libsinsp_e2e/sys_call_test.cpp index 2bdf22bb44..eb6a688643 100644 --- a/test/libsinsp_e2e/sys_call_test.cpp +++ b/test/libsinsp_e2e/sys_call_test.cpp @@ -23,6 +23,7 @@ limitations under the License. #include #include +#include #include #include @@ -351,6 +352,92 @@ TEST_F(sys_call_test, close_badfd_dropping) EXPECT_EQ(0, callnum); } +// The poll syscall is not defined on arm64. +#if !defined(__aarch64__) +TEST_F(sys_call_test, poll_timeout) +{ + int callnum = 0; + + event_filter_t filter = [&](sinsp_evt* evt) + { + uint16_t type = evt->get_type(); + auto ti = evt->get_thread_info(false); + return (type == PPME_SYSCALL_POLL_E || + type == PPME_SYSCALL_POLL_X) && + ti->m_comm == "test_helper"; + }; + + std::string my_pipe[2]; + + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + subprocess handle(LIBSINSP_TEST_PATH "/test_helper", {"poll_timeout"}); + std::stringstream ss; + ss << handle.out(); + my_pipe[0] = ss.str(); + ss.clear(); + ss.str(" "); + ss << handle.out(); + my_pipe[1] = ss.str(); + ss.clear(); + ss.str(" "); + handle.wait(); + }; + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_POLL_E) + { + // + // stdin and stdout can be a file or a fifo depending + // on how the tests are invoked + // + std::string fds = e->get_param_value_str("fds"); + std::string expected_fds = my_pipe[0] + ":p1 " + + my_pipe[1] + ":p4"; + EXPECT_EQ(expected_fds, fds) + << "Value of fds is not one of expected values: " << fds; + EXPECT_EQ("20", e->get_param_value_str("timeout")); + callnum++; + } + else if (type == PPME_SYSCALL_POLL_X) + { + std::string fds = e->get_param_value_str("fds"); + std::string expected_fds = my_pipe[0] + ":p0 " + + my_pipe[1] + ":p4"; + int64_t res = std::stol(e->get_param_value_str("res")); + + EXPECT_GT(res, 0); + EXPECT_LE(res, 2); + + switch (res) + { + case 1: + EXPECT_EQ(expected_fds, fds) + << "Value of fds is not one of expected values: " << fds; + ; + break; + case 2: + // + // On EC2 called from jenkins stdin returns POLLHUP + // + EXPECT_EQ(expected_fds, fds) + << "Value of fds is not one of expected values: " << fds; + break; + default: + FAIL(); + } + + callnum++; + } + }; + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(2, callnum); +} +#endif + TEST(inspector, invalid_file_name) { sinsp inspector; @@ -1665,6 +1752,123 @@ TEST_F(sys_call_test, failing_execve) EXPECT_EQ(2, callnum); } +TEST_F(sys_call_test, large_execve) +{ + const int buf_size = 100 * 1024; + const int driver_truncation_size = getpagesize(); + const string non_existing_binary = "/non/existent"; + const string existing_binary = "/bin/true"; + + int ctid; + int callnum = 0; + + event_filter_t filter = [&](sinsp_evt* evt) { return evt->get_tid() == ctid; }; + + srandom(42); + + string buf; + while (buf.length() < buf_size) + { + buf.append(std::to_string(random())); + } + + run_callback_t test = [&](concurrent_object_handle inspector) + { + ctid = fork(); + + if (ctid < 0) + { + FAIL(); + } + + if (ctid == 0) + { + { + const char* eargv[] = {non_existing_binary.c_str(), buf.c_str(), NULL}; + + const char* eenvp[] = {buf.c_str(), NULL}; + + int ret = execve(eargv[0], (char* const*)eargv, (char* const*)eenvp); + ASSERT_TRUE(ret < 0); + } + + { + const char* eargv[] = {existing_binary.c_str(), buf.c_str(), NULL}; + + const char* eenvp[] = {buf.c_str(), NULL}; + + int ret = execve(eargv[0], (char* const*)eargv, (char* const*)eenvp); + ASSERT_TRUE(ret == 0); + } + } + else + { + wait(NULL); + sleep(1); + } + }; + + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_EXECVE_19_E || type == PPME_SYSCALL_EXECVE_18_E) + { + ++callnum; + + string filename = e->get_param_value_str("filename"); + + if (callnum == 1) + { + EXPECT_EQ(filename, non_existing_binary); + } + else if (callnum == 3) + { + EXPECT_EQ(filename, existing_binary); + } + else + { + FAIL(); + } + } + else if (type == PPME_SYSCALL_EXECVE_19_X || type == PPME_SYSCALL_EXECVE_18_X) + { + ++callnum; + + string exe = e->get_param_value_str("exe"); + string args = e->get_param_value_str("args"); + + if (callnum == 2) + { + // This is the failed execve. exe and + // args will be available, but env + // will not. + EXPECT_EQ(exe, non_existing_binary.c_str()); + EXPECT_EQ(args, + buf.substr(0, driver_truncation_size - non_existing_binary.length() - 2) + "."); + } + else if (callnum == 4) + { + string env = e->get_param_value_str("env"); + + EXPECT_EQ(exe, existing_binary); + EXPECT_EQ( + args, + buf.substr(0, driver_truncation_size - existing_binary.length() - 2) + "."); + EXPECT_EQ(env, buf.substr(0, driver_truncation_size - 1) + "."); + } + else + { + FAIL(); + } + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(4, callnum); +} + #ifdef __x86_64__ TEST_F(sys_call_test32, failing_execve) @@ -1771,4 +1975,537 @@ TEST_F(sys_call_test32, failing_execve) EXPECT_EQ(10, callnum); } +TEST_F(sys_call_test32, mmap) +{ + int callnum = 0; + int errno2; + + proc_started_filter ps_filter; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + auto tinfo = evt->get_thread_info(false); + return tinfo && tinfo->m_comm == "test_helper_32" + && ps_filter(evt); + }; + + uint64_t p = 0; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + subprocess handle(LIBSINSP_TEST_PATH "/test_helper_32", + {"mmap_test",}); + std::stringstream tmp; + handle.out(); + tmp << handle.out(); + errno2 = std::stoi(tmp.str()); + tmp.clear(); + tmp.str(""); + tmp << handle.out(); + p = (uint64_t)std::stoul(tmp.str()); + tmp.clear(); + tmp.str(""); + handle.wait(); + }; + + uint32_t enter_vmsize; + uint32_t enter_vmrss; + uint32_t exit_vmsize; + uint32_t exit_vmrss; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_MUNMAP_E) + { + callnum++; + + enter_vmsize = e->get_thread_info(false)->m_vmsize_kb; + enter_vmrss = e->get_thread_info(false)->m_vmrss_kb; + + switch (callnum) + { + case 1: + EXPECT_EQ("50", e->get_param_value_str("addr")); + EXPECT_EQ("300", e->get_param_value_str("length")); + break; + case 7: + { + uint64_t addr = 0; + memcpy(&addr, e->get_param_by_name("addr")->m_val, sizeof(uint64_t)); +#ifdef __LP64__ + EXPECT_EQ((uint64_t)p, addr); +#else + EXPECT_EQ(((uint32_t)p), addr); #endif + EXPECT_EQ("1003520", e->get_param_value_str("length")); + break; + } + default: + callnum--; + } + } + else if (type == PPME_SYSCALL_MUNMAP_X) + { + callnum++; + + memcpy(&exit_vmsize, e->get_param_by_name("vm_size")->m_val, sizeof(uint32_t)); + memcpy(&exit_vmrss, e->get_param_by_name("vm_rss")->m_val, sizeof(uint32_t)); + EXPECT_EQ(e->get_thread_info(false)->m_vmsize_kb, exit_vmsize); + EXPECT_EQ(e->get_thread_info(false)->m_vmrss_kb, exit_vmrss); + + switch (callnum) + { + case 2: + EXPECT_EQ("EINVAL", e->get_param_value_str("res")); + EXPECT_EQ("-22", e->get_param_value_str("res", false)); + break; + case 8: + EXPECT_EQ("0", e->get_param_value_str("res")); + EXPECT_GT(enter_vmsize, exit_vmsize + 500); + EXPECT_GE(enter_vmrss, enter_vmrss); + break; + default: + callnum--; + } + } + else if (type == PPME_SYSCALL_MMAP_E || type == PPME_SYSCALL_MMAP2_E) + { + callnum++; + + enter_vmsize = e->get_thread_info(false)->m_vmsize_kb; + enter_vmrss = e->get_thread_info(false)->m_vmrss_kb; + + switch (callnum) + { + case 3: + EXPECT_EQ("0", e->get_param_value_str("addr")); + EXPECT_EQ("0", e->get_param_value_str("length")); + EXPECT_EQ("PROT_READ|PROT_WRITE|PROT_EXEC", e->get_param_value_str("prot")); + EXPECT_EQ("MAP_SHARED|MAP_PRIVATE|MAP_ANONYMOUS|MAP_DENYWRITE", + e->get_param_value_str("flags")); + EXPECT_EQ("-1", e->get_param_value_str("fd", false)); + + if (type == PPME_SYSCALL_MMAP_E) + { + EXPECT_EQ("0", e->get_param_value_str("offset")); + } + else + { + EXPECT_EQ("0", e->get_param_value_str("pgoffset")); + } + break; + case 5: + EXPECT_EQ("0", e->get_param_value_str("addr")); + EXPECT_EQ("1003520", e->get_param_value_str("length")); + EXPECT_EQ("PROT_READ|PROT_WRITE", e->get_param_value_str("prot")); + EXPECT_EQ("MAP_PRIVATE|MAP_ANONYMOUS", e->get_param_value_str("flags")); + EXPECT_EQ("-1", e->get_param_value_str("fd", false)); + + if (type == PPME_SYSCALL_MMAP_E) + { + EXPECT_EQ("0", e->get_param_value_str("offset")); + } + else + { + EXPECT_EQ("0", e->get_param_value_str("pgoffset")); + } + break; + default: + callnum--; + } + } + else if (type == PPME_SYSCALL_MMAP_X || type == PPME_SYSCALL_MMAP2_X) + { + callnum++; + + memcpy(&exit_vmsize, e->get_param_by_name("vm_size")->m_val, sizeof(uint32_t)); + memcpy(&exit_vmrss, e->get_param_by_name("vm_rss")->m_val, sizeof(uint32_t)); + EXPECT_EQ(e->get_thread_info(false)->m_vmsize_kb, exit_vmsize); + EXPECT_EQ(e->get_thread_info(false)->m_vmrss_kb, exit_vmrss); + + switch (callnum) + { + case 4: + { + uint64_t res = 0; + memcpy(&res, e->get_param_by_name("res")->m_val, sizeof(uint64_t)); + EXPECT_EQ(-errno2, (int64_t)res); + break; + } + case 6: + { + uint64_t res = 0; + memcpy(&res, e->get_param_by_name("res")->m_val, sizeof(uint64_t)); + EXPECT_EQ((uint64_t)p, res); + EXPECT_GT(exit_vmsize, enter_vmsize + 500); + EXPECT_GE(exit_vmrss, enter_vmrss); + break; + } + default: + callnum--; + } + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(8, callnum); +} + +TEST_F(sys_call_test32, ppoll_timeout) +{ + int callnum = 0; + event_filter_t filter = [&](sinsp_evt* evt) + { + auto tinfo = evt->get_thread_info(false); + return (evt->get_type() == PPME_SYSCALL_PPOLL_E || + evt->get_type() == PPME_SYSCALL_PPOLL_X) && + tinfo != nullptr && tinfo->m_comm == "test_helper_32"; + }; + + std::string my_pipe[2]; + + run_callback_t test = [&](concurrent_object_handle inspector) + { + subprocess handle(LIBSINSP_TEST_PATH "/test_helper_32", + {"ppoll_timeout",}); + std::stringstream ss; + ss << handle.out(); + my_pipe[0] = ss.str(); + ss.clear(); + ss.str(" "); + ss << handle.out(); + my_pipe[1] = ss.str(); + ss.clear(); + ss.str(" "); + handle.wait(); + }; + + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_PPOLL_E) + { + // + // stdin and stdout can be a file or a fifo depending + // on how the tests are invoked + // + std::string fds = e->get_param_value_str("fds"); + std::string expected_fds = my_pipe[0] + ":p1 " + + my_pipe[1] + ":p4"; + + EXPECT_EQ(expected_fds, fds); + EXPECT_EQ("1000000", e->get_param_value_str("timeout", false)); + EXPECT_EQ("SIGHUP SIGCHLD", e->get_param_value_str("sigmask", false)); + callnum++; + } + else if (type == PPME_SYSCALL_PPOLL_X) + { + int64_t res = std::stol(e->get_param_value_str("res")); + + EXPECT_GT(res, 0); + EXPECT_LE(res, 2); + + string fds = e->get_param_value_str("fds"); + std::string expected_fds = my_pipe[0] + ":p0 " + + my_pipe[1] + ":p4"; + + switch (res) + { + case 1: + EXPECT_EQ(expected_fds, fds); + break; + case 2: + // + // On EC2 called from jenkins stdin returns POLLHUP + // + EXPECT_EQ(expected_fds, fds); + break; + default: + FAIL(); + } + + callnum++; + } + }; + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(2, callnum); +} + +TEST_F(sys_call_test32, fs_preadv) +{ + int callnum = 0; + int fd = 0; + int fd1 = 0; + bool pwritev64_succeeded; + bool pwritev64_succeeded2; + proc_started_filter test_started_filter; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + auto tinfo = evt->get_thread_info(false); + if (tinfo && tinfo->m_comm == "test_helper_32") + { + auto type = evt->get_type(); + return (type == PPME_SYSCALL_PREADV_E + || type == PPME_SYSCALL_PREADV_X + || type == PPME_SYSCALL_PWRITEV_E + || type == PPME_SYSCALL_PWRITEV_X); + } + return false; + }; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + subprocess test_proc(LIBSINSP_TEST_PATH "/test_helper_32", {"preadv_pwritev"}); + fd = std::stoi(test_proc.out()); + int bool_n = std::stoi(test_proc.out()); + pwritev64_succeeded = (bool_n == 1); + bool_n = std::stoi(test_proc.out()); + pwritev64_succeeded2 = (bool_n == 1); + fd1 = std::stoi(test_proc.out()); + test_proc.wait(); + }; + + int pwrite1_res = 0; + int pwrite2_res = 0; + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_PWRITEV_E) + { + if (callnum == 0) + { + EXPECT_EQ(fd, std::stoll(e->get_param_value_str("fd", false))); + EXPECT_EQ(987654321, std::stoll(e->get_param_value_str("pos"))); + EXPECT_EQ(15, std::stoll(e->get_param_value_str("size"))); + callnum++; + } + else + { + EXPECT_EQ(fd, std::stoll(e->get_param_value_str("fd", false))); + EXPECT_EQ(10, std::stoll(e->get_param_value_str("pos"))); + EXPECT_EQ(15, std::stoll(e->get_param_value_str("size"))); + callnum++; + } + } + else if (type == PPME_SYSCALL_PWRITEV_X) + { + if (callnum == 1) + { + pwrite1_res = std::stoi(e->get_param_value_str("res", false)); + EXPECT_EQ("aaaaabbbbbccccc", e->get_param_value_str("data")); + callnum++; + } + else + { + pwrite2_res = std::stoi(e->get_param_value_str("res", false)); + EXPECT_EQ("aaaaabbbbbccccc", e->get_param_value_str("data")); + callnum++; + } + } + else if (type == PPME_SYSCALL_PREADV_E) + { + if (callnum == 4) + { + EXPECT_EQ(fd1, std::stoll(e->get_param_value_str("fd", false))); + EXPECT_EQ(987654321, std::stoll(e->get_param_value_str("pos"))); + callnum++; + } + else + { + EXPECT_EQ(fd1, std::stoll(e->get_param_value_str("fd", false))); + EXPECT_EQ(10, std::stoll(e->get_param_value_str("pos"))); + callnum++; + } + } + else if (type == PPME_SYSCALL_PREADV_X) + { + if (callnum == 3) + { + EXPECT_EQ(15, std::stoi(e->get_param_value_str("res", false))); + EXPECT_EQ("aaaaabbbbb", e->get_param_value_str("data")); + EXPECT_EQ(30, std::stoll(e->get_param_value_str("size"))); + callnum++; + } + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(6, callnum); + if (pwritev64_succeeded) + { + EXPECT_EQ(15, pwrite1_res); + } + else + { + EXPECT_GT(0, pwrite1_res); + } + if (pwritev64_succeeded2) + { + EXPECT_EQ(15, pwrite2_res); + } + else + { + EXPECT_EQ(-22, pwrite2_res); + } +} +#endif + +extern "C" +{ + int32_t scap_proc_read_thread(struct scap_linux_platform* linux_platform, + char* procdirname, + uint64_t tid, + struct scap_threadinfo* tinfo, + char* error, + bool scan_sockets); +} + +TEST_F(sys_call_test, thread_lookup_static) +{ + char err_buf[SCAP_LASTERR_SIZE]; + scap_threadinfo scap_tinfo; + char proc[] = LIBSINSP_TEST_RESOURCES_PATH "/_proc"; + struct stat s = {}; + if (stat(proc, &s) != 0) + { + fprintf(stderr, "%s not found, skipping test\n", proc); + FAIL(); + } + + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_type() != PPME_PROCEXIT_1_E && evt->get_tid() > 0; }; + run_callback_t test = [&](concurrent_object_handle inspector) {return;}; + captured_event_callback_t callback = [&](const callback_param& param) {return;}; + scap_linux_platform *platform; + + before_close_t before_close = [&](sinsp* inspector) + { + platform = (scap_linux_platform*)inspector->get_scap_platform(); + }; + + ASSERT_NO_FATAL_FAILURE( + { event_capture::run(test, callback, filter, event_capture::do_nothing, before_close); }); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, 1, &scap_tinfo, err_buf, false)); + + EXPECT_EQ(1, scap_tinfo.tid); + EXPECT_EQ(1, scap_tinfo.pid); + EXPECT_EQ(1, scap_tinfo.vtid); + EXPECT_EQ(0, scap_tinfo.ptid); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, 62725, &scap_tinfo, err_buf, false)); + EXPECT_EQ(62725, scap_tinfo.tid); + EXPECT_EQ(62725, scap_tinfo.pid); + EXPECT_EQ(62725, scap_tinfo.vtid); + EXPECT_EQ(1, scap_tinfo.ptid); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, 62727, &scap_tinfo, err_buf, false)); + EXPECT_EQ(62727, scap_tinfo.tid); + EXPECT_EQ(62725, scap_tinfo.pid); + EXPECT_EQ(62727, scap_tinfo.vtid); + EXPECT_EQ(1, scap_tinfo.ptid); + +} + +TEST_F(sys_call_test, thread_lookup_live) +{ + char err_buf[SCAP_LASTERR_SIZE]; + scap_threadinfo scap_tinfo; + char proc[] = "/proc"; + + std::unordered_set seen_tids; + + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_type() != PPME_PROCEXIT_1_E && evt->get_tid() > 0; }; + run_callback_t test = [&](concurrent_object_handle inspector) + { + // a very short sleep to gather some events, + // we'll take much longer than this to process them all + usleep(1000); + }; + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + auto tid = e->get_tid(); + if (!seen_tids.insert(tid).second) + { + return; + } + fprintf(stderr, "looking up tid %ld in /proc\n", tid); + // In some cases scap_proc_read_thread can return SCAP_SUCCESS without + // filling in scap_tinfo + if (scap_proc_read_thread((scap_linux_platform*)param.m_inspector->get_scap_platform(), + proc, tid, &scap_tinfo, err_buf, false) == SCAP_SUCCESS) + { + auto tinfo = e->get_thread_info(false); + if (!tinfo) + { + return; + } + EXPECT_NE(0, scap_tinfo.tid); + EXPECT_NE(0, scap_tinfo.pid); + EXPECT_NE(0, scap_tinfo.vtid); + EXPECT_EQ(tinfo->m_tid, scap_tinfo.tid); + EXPECT_EQ(tinfo->m_pid, scap_tinfo.pid); + EXPECT_EQ(tinfo->m_vtid, scap_tinfo.vtid); + // Not testing scap_tinfo.ptid because it can change in between event and lookup + } + }; + + scap_linux_platform *platform; + + before_close_t before_close = [&](sinsp* inspector) + { + // close scap to maintain the num_consumers at exit == 0 assertion + //close_capture(scap, platform); + platform = (scap_linux_platform*)inspector->get_scap_platform(); + }; + ASSERT_NO_FATAL_FAILURE( + { event_capture::run(test, callback, filter, event_capture::do_nothing, before_close); }); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, getpid(), + &scap_tinfo, err_buf, false)); + EXPECT_EQ(getpid(), scap_tinfo.tid); + EXPECT_EQ(getpid(), scap_tinfo.pid); + EXPECT_EQ(getpid(), scap_tinfo.vtid); + EXPECT_EQ(getppid(), scap_tinfo.ptid); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, 1, + &scap_tinfo, err_buf, false)); + EXPECT_EQ(1, scap_tinfo.tid); + EXPECT_EQ(1, scap_tinfo.pid); + EXPECT_EQ(1, scap_tinfo.vtid); + EXPECT_EQ(0, scap_tinfo.ptid); + +} diff --git a/test/libsinsp_e2e/tcp_client_server.cpp b/test/libsinsp_e2e/tcp_client_server.cpp index 5901e02301..d5a4656f02 100644 --- a/test/libsinsp_e2e/tcp_client_server.cpp +++ b/test/libsinsp_e2e/tcp_client_server.cpp @@ -159,8 +159,7 @@ void runtest(iotype iot, if (evt->get_fd_info()->m_type != SCAP_FD_IPV4_SOCK) { // - // Skip non-tcp sockets. Python opens unix sockets - // to god knows what. + // Skip non-tcp sockets. Python opens unix sockets. // return; } @@ -308,7 +307,10 @@ void runtest(iotype iot, // // OUTPUT VALDATION // - ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + ASSERT_NO_FATAL_FAILURE({event_capture::run(test, callback, filter, event_capture::do_nothing, + event_capture::do_nothing, event_capture::always_continue, 131072, + (uint64_t)60 * 1000 * 1000 * 1000, (uint64_t)60 * 1000 * 1000 * 1000, + SINSP_MODE_LIVE, 3, false); }); ASSERT_GT(callnum,0); } @@ -406,7 +408,10 @@ TEST_F(sys_call_test, tcp_client_server_with_connection_before_capturing_starts) server.wait_till_ready(); client.wait_till_ready(); - ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + ASSERT_NO_FATAL_FAILURE({event_capture::run(test, callback, filter, event_capture::do_nothing, + event_capture::do_nothing, event_capture::always_continue, 131072, + (uint64_t)60 * 1000 * 1000 * 1000, (uint64_t)60 * 1000 * 1000 * 1000, + SINSP_MODE_LIVE, 3, false); }); ASSERT_EQ(1, state); } diff --git a/test/libsinsp_e2e/tcp_client_server_ipv4_mapped.cpp b/test/libsinsp_e2e/tcp_client_server_ipv4_mapped.cpp index 6178aa602f..32cf874b99 100644 --- a/test/libsinsp_e2e/tcp_client_server_ipv4_mapped.cpp +++ b/test/libsinsp_e2e/tcp_client_server_ipv4_mapped.cpp @@ -670,7 +670,10 @@ void runtest_ipv4m(iotype iot, } }; - ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + ASSERT_NO_FATAL_FAILURE({event_capture::run(test, callback, filter, event_capture::do_nothing, + event_capture::do_nothing, event_capture::always_continue, 131072, + (uint64_t)60 * 1000 * 1000 * 1000, (uint64_t)60 * 1000 * 1000 * 1000, + SINSP_MODE_LIVE, 3, false); }); } TEST_F(sys_call_test, tcp_client_server_ipv4m) diff --git a/test/libsinsp_e2e/test_helper.cpp b/test/libsinsp_e2e/test_helper.cpp index ec41c445ab..c9ae1ca7e5 100644 --- a/test/libsinsp_e2e/test_helper.cpp +++ b/test/libsinsp_e2e/test_helper.cpp @@ -43,6 +43,7 @@ limitations under the License. #include #include +#include #include #include @@ -68,6 +69,10 @@ void mmap_test(const vector& args) { int errno2; void* p; + + printf("STARTED\n"); + fflush(stdout); + munmap((void*)0x50, 300); p = mmap(0, 0, @@ -78,7 +83,10 @@ void mmap_test(const vector& args) errno2 = errno; p = mmap(NULL, 1003520, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); munmap(p, 1003520); - cout << errno2 << " " << p << endl; + printf("%d\n", errno2); + fflush(stdout); + printf("%u\n", p); + fflush(stdout); } bool str_to_bool(const string& s) @@ -182,6 +190,8 @@ void preadv_pwritev(const vector& args) // bool pwritev64_succeeded = bytes_sent > 0; + cout << fd << endl; + cout << (pwritev64_succeeded ? 1 : 0) << endl; bytes_sent = pwritev(fd, wv, wv_count, 10); @@ -192,6 +202,8 @@ void preadv_pwritev(const vector& args) auto fd1 = open(FILENAME, O_CREAT | O_RDONLY, S_IRWXU); + cout << fd1 << endl; + wv[0].iov_len = sizeof(msg1); wv[1].iov_len = sizeof(msg2); wv[2].iov_len = sizeof(msg3); @@ -223,11 +235,12 @@ void quotactl_ok(const vector& args) { struct dqblk mydqblk; struct dqinfo mydqinfo; + std::string caddr = args[0] + "/aquota.user"; quotactl(QCMD(Q_QUOTAON, USRQUOTA), - "/dev/loop0", + args[1].c_str(), 2, - (caddr_t) "/tmp/testquotamnt/aquota.user"); // 2 => QFMT_VFS_V0 - quotactl(QCMD(Q_GETQUOTA, USRQUOTA), "/dev/loop0", 0, (caddr_t)&mydqblk); // 0 => root user + (caddr_t)caddr.c_str()); // 2 => QFMT_VFS_V0 + quotactl(QCMD(Q_GETQUOTA, USRQUOTA), args[1].c_str(), 0, (caddr_t)&mydqblk); // 0 => root user fwrite(&mydqblk.dqb_bhardlimit, 1, sizeof(uint64_t), stdout); fwrite(&mydqblk.dqb_bsoftlimit, 1, sizeof(uint64_t), stdout); fwrite(&mydqblk.dqb_curspace, 1, sizeof(uint64_t), stdout); @@ -235,10 +248,33 @@ void quotactl_ok(const vector& args) fwrite(&mydqblk.dqb_isoftlimit, 1, sizeof(uint64_t), stdout); fwrite(&mydqblk.dqb_btime, 1, sizeof(uint64_t), stdout); fwrite(&mydqblk.dqb_itime, 1, sizeof(uint64_t), stdout); - quotactl(QCMD(Q_GETINFO, USRQUOTA), "/dev/loop0", 0, (caddr_t)&mydqinfo); + quotactl(QCMD(Q_GETINFO, USRQUOTA), args[1].c_str(), 0, (caddr_t)&mydqinfo); fwrite(&mydqinfo.dqi_bgrace, 1, sizeof(uint64_t), stdout); fwrite(&mydqinfo.dqi_igrace, 1, sizeof(uint64_t), stdout); - quotactl(QCMD(Q_QUOTAOFF, USRQUOTA), "/dev/loop0", 0, NULL); + quotactl(QCMD(Q_QUOTAOFF, USRQUOTA), args[1].c_str(), 0, NULL); +} + +void poll_timeout(const vector& args) +{ + int my_pipe[2]; + auto ret = pipe(my_pipe); + if (ret != 0) + { + return; + } + + struct pollfd ufds[2]; + ufds[0].fd = my_pipe[0]; + ufds[0].events = POLLIN; + ufds[1].fd = my_pipe[1]; + ufds[1].events = POLLOUT; + + poll(ufds, 2, 20); + + printf("%d\n", my_pipe[0]); + fflush(stdout); + printf("%d\n", my_pipe[1]); + fflush(stdout); } void ppoll_timeout(const vector& args) @@ -265,6 +301,11 @@ void ppoll_timeout(const vector& args) sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGCHLD); ppoll(ufds, 2, &timeout, &sigs); + + printf("%d\n", my_pipe[0]); + fflush(stdout); + printf("%d\n", my_pipe[1]); + fflush(stdout); } void pgid_test(const vector& args) @@ -710,6 +751,7 @@ const unordered_map&)>> func_map = { {"preadv_pwritev", preadv_pwritev}, {"quotactl_ko", quotactl_ko}, {"quotactl_ok", quotactl_ok}, + {"poll_timeout", poll_timeout}, {"ppoll_timeout", ppoll_timeout}, {"pgid_test", pgid_test}, {"custom_container", custom_container}, diff --git a/test/libsinsp_e2e/unix_client_server.cpp b/test/libsinsp_e2e/unix_client_server.cpp new file mode 100644 index 0000000000..9dc537033b --- /dev/null +++ b/test/libsinsp_e2e/unix_client_server.cpp @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include "event_capture.h" +#include "subprocess.h" +#include "sys_call_test.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define NAME "/tmp/python_unix_sockets_example" +#define PAYLOAD "0123456789QWERTYUIOPASDFGHJKLZXCVBNM" + +#define PAYLOAD "0123456789QWERTYUIOPASDFGHJKLZXCVBNM" +#define BUFFER_LENGTH (sizeof(PAYLOAD) - 1) +#define FALSE 0 + +inline void parse_tuple(const std::string& tuple, + std::string& srcstr, + std::string& dststr, bool shift = false) +{ + std::string token; + std::stringstream ss(tuple); + std::vector tst; + + int base = shift? 1 : 0; + + while (std::getline(ss, token, '>')) { + tst.push_back(token); + } + + int size = shift? 3 : 2; + EXPECT_EQ(size, (int)tst.size()); + + srcstr = tst[base].substr(0, tst[base].size() - 1); + dststr = tst[base+1]; +} + +inline bool ends_with(const std::string& value, const std::string& ending) +{ + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +TEST_F(sys_call_test, unix_client_server) +{ + int32_t callnum = 0; + bool first_connect_or_accept_seen = true; + std::string sport; + std::string src_addr; + std::string dest_addr; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + sinsp_threadinfo* ti = evt->get_thread_info(false); + if (ti) + { + if (ti->get_comm() == "python2" && ti->m_args.size() >= 1) + { + return ends_with(ti->m_args[0],"unix_client_server.py") || + ends_with(ti->m_args[0],"unix_client_server.py"); + } + } + + return false; + }; + + // + // INITIALIZATION + // + run_callback_t test = [](concurrent_object_handle inspector) + { + subprocess server("python2", {LIBSINSP_TEST_RESOURCES_PATH "/unix_client_server.py", "server"}); + + server.wait_for_start(); + + subprocess client("python2", {LIBSINSP_TEST_RESOURCES_PATH "/unix_client_server.py", "client"}); + server.wait(); + client.wait(); + }; + + // + // OUTPUT VALIDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* evt = param.m_evt; + + //std::cout << evt->get_name() << std::endl; + + if (evt->get_type() == PPME_SOCKET_CONNECT_X) + { + std::string tuple = evt->get_param_value_str("tuple"); + std::string addrs = tuple.substr(0, tuple.find(" ")); + std::string file = tuple.substr(tuple.find(" ") + 1); + + EXPECT_EQ(NAME, file); + + std::string srcstr; + std::string dststr; + parse_tuple(tuple, srcstr, dststr); + + EXPECT_NE("0000000000000000", srcstr); + EXPECT_NE("0000000000000000", dststr); + + // + // connect() and accept() can return + // in a different order + // + if (first_connect_or_accept_seen) + { + first_connect_or_accept_seen = false; + src_addr = srcstr.substr(1); + dest_addr = dststr; + } + else + { + EXPECT_EQ(src_addr, srcstr.substr(1)); + EXPECT_EQ(dest_addr, dststr); + } + + callnum++; + } + else if ((evt->get_type() == PPME_SOCKET_ACCEPT_5_X) || + (evt->get_type() == PPME_SOCKET_ACCEPT4_6_X)) + { + std::string tuple = evt->get_param_value_str("tuple"); + std::string addrs = tuple.substr(0, tuple.find(" ")); + std::string file = tuple.substr(tuple.find(" ") + 1); + + EXPECT_EQ(NAME, file); + + std::string srcstr; + std::string dststr; + parse_tuple(tuple, srcstr, dststr); + + EXPECT_NE("0000000000000000", srcstr); + EXPECT_NE("0000000000000000", dststr); + + // + // connect() and accept() can return + // in a different order + // + if (first_connect_or_accept_seen) + { + first_connect_or_accept_seen = false; + src_addr = srcstr.substr(1); + dest_addr = dststr; + } + else + { + EXPECT_EQ(src_addr, srcstr.substr(1)); + EXPECT_EQ(dest_addr, dststr); + } + + std::string fdtuple = evt->get_param_value_str("tuple"); + std::string fdaddrs = fdtuple.substr(0, fdtuple.find(" ")); + std::string fdfile = fdtuple.substr(fdtuple.find(" ") + 1); + + EXPECT_EQ(NAME, fdfile); + + std::string fdsrcstr; + std::string fddststr; + parse_tuple(tuple, fdsrcstr, fddststr); + + EXPECT_NE("0000000000000000", fdsrcstr); + EXPECT_NE("0000000000000000", fddststr); + + callnum++; + } + + if (callnum < 1) + { + return; + } + + // + // 32bit (and s390x) uses send() and recv(), while 64bit + // uses sendto() and recvfrom() and sets the address to NULL + // + if (evt->get_type() == PPME_SOCKET_SEND_E || evt->get_type() == PPME_SOCKET_RECV_E || + evt->get_type() == PPME_SOCKET_SENDTO_E || evt->get_type() == PPME_SOCKET_RECVFROM_E) + { + if (((evt->get_type() == PPME_SOCKET_RECVFROM_X) || + (evt->get_type() == PPME_SOCKET_RECVFROM_X)) && + (evt->get_param_value_str("tuple") != "")) + { + EXPECT_EQ("NULL", evt->get_param_value_str("tuple")); + } + + std::string fdtuple = evt->get_param_value_str("fd"); + std::string fdaddrs = fdtuple.substr(0, fdtuple.find(" ")); + std::string fdfile = fdtuple.substr(fdtuple.find(" ") + 1); + + EXPECT_EQ(NAME, fdfile); + + std::string fdsrcstr; + std::string fddststr; + parse_tuple(fdtuple, fdsrcstr, fddststr, true); + + EXPECT_NE("0", fdsrcstr); + EXPECT_NE("0", fddststr); + + callnum++; + } + else if ((evt->get_type() == PPME_SOCKET_RECV_X) || + (evt->get_type() == PPME_SOCKET_RECVFROM_X)) + { + if (evt->get_type() == PPME_SOCKET_RECVFROM_X) + { + if (callnum == 5) + { + std::string tuple = evt->get_param_value_str("tuple"); + std::string addrs = tuple.substr(0, tuple.find(" ")); + std::string file = tuple.substr(tuple.find(" ") + 1); + + EXPECT_EQ(NAME, file); + + std::string srcstr; + std::string dststr; + parse_tuple(tuple, srcstr, dststr); + + EXPECT_NE("0000000000000000", srcstr); + EXPECT_NE("0000000000000000", dststr); + } + } + EXPECT_EQ(PAYLOAD, evt->get_param_value_str("data")); + + callnum++; + } + }; + + // + // OUTPUT VALDATION + // + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_FALSE(first_connect_or_accept_seen); + EXPECT_EQ(8, callnum); +} +