From 9ab690b7c0b6a3998540500720ecb8e0b74d7018 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Tue, 15 Aug 2023 22:50:25 +0100 Subject: [PATCH] Added some memory safety tests using valgrind. --- autoconf/scripts/run-valgrind.sh | 61 +++++++++++++++++++ tests/Memory_safety_-_Basic.sh | 38 ++++++++++++ ...Memory_safety_-_Remote_control_receiver.sh | 43 +++++++++++++ .../Memory_safety_-_Remote_control_sender.sh | 39 ++++++++++++ 4 files changed, 181 insertions(+) create mode 100755 autoconf/scripts/run-valgrind.sh create mode 100644 tests/Memory_safety_-_Basic.sh create mode 100644 tests/Memory_safety_-_Remote_control_receiver.sh create mode 100644 tests/Memory_safety_-_Remote_control_sender.sh diff --git a/autoconf/scripts/run-valgrind.sh b/autoconf/scripts/run-valgrind.sh new file mode 100755 index 0000000..7957070 --- /dev/null +++ b/autoconf/scripts/run-valgrind.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# +# Function to launch the test subject under valgrind. +# +# If valgrind is unavailable, exits the script with status 2, after writing +# a note to file descriptor 4. +# +# If valgrind finds an error, writes the error to "valgrind.out" in the +# current directory, and exits the script with status 1 after writing a note +# to file descriptor 4. +# +# If valgrind does not find any errors, the function returns with the exit +# status of the test subject. +# +# Source this file from test scripts that use valgrind. +# +# Requires ${testSubject} and ${workFile4}. This means that the caller must +# not use file ${workFile4}, as this function will overwrite it. +# + +# Output file for failures. +valgrindOutputFile="valgrind.out" + +# Dummy assignments for "shellcheck". +testSubject="${testSubject:-false}"; workFile4="${workFile4:-.tmp4}" + +if ! command -v valgrind >/dev/null 2>&1; then + echo "test requires \`valgrind'" + exit 2 +fi + +runWithValgrind () { + + valgrind --tool=memcheck \ + --verbose --show-error-list=yes --log-fd=3 \ + --error-exitcode=125 \ + --track-fds=yes \ + --leak-check=full \ + "${testSubject}" "$@" \ + 3>"${workFile4}" 4<&- + + returnValue=$? + + if test "${returnValue}" -eq 125; then + { + echo "================================================" + date + echo "Command: ${testSubject} $*" + echo + cat "${workFile4}" + echo "================================================" + echo + } >> "${valgrindOutputFile}" + echo "memory check failed - see file \`valgrind.out'." 1>&4 + exit 1 + fi + + return "${returnValue}" +} + +# EOF diff --git a/tests/Memory_safety_-_Basic.sh b/tests/Memory_safety_-_Basic.sh new file mode 100644 index 0000000..2c6ea91 --- /dev/null +++ b/tests/Memory_safety_-_Basic.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Run valgrind's memory checker against a process using various different +# transfer options. + +# Dummy assignments for "shellcheck". +sourcePath="${sourcePath:-.}"; testSubject="${testSubject:-false}"; workFile1="${workFile1:-.tmp1}"; workFile2="${workFile2:-.tmp2}"; workFile3="${workFile3:-.tmp3}"; workFile4="${workFile4:-.tmp4}" + +# Load the valgrind function. +. "${sourcePath}/autoconf/scripts/run-valgrind.sh" + +# Plain, no options. +{ echo "testing" | runWithValgrind >/dev/null 2>&1; } 4>&1 || exit 1 + +# With --force. +{ echo "testing" | runWithValgrind -f >/dev/null 2>&1; } 4>&1 || exit 1 + +# Check "--average-rate". +(dd if=/dev/zero bs=100 count=1 2>/dev/null; + sleep 1; + dd if=/dev/zero bs=110 count=1 2>/dev/null; + sleep 1; +) | { runWithValgrind -f -i 0.5 -a >/dev/null 2>"${workFile1}"; } 4>&1 || exit 1 + +# Check "--buffer-size" and "--buffer-percent". +{ \ + ( dd if=/dev/zero bs=1024 count=1024; sleep 2; ) 2>/dev/null \ + | runWithValgrind -f -T -i 0.5 -B 1024 2>"${workFile1}" \ + | (sleep 1; dd bs=1024 count=512; sleep 1; cat; ) >/dev/null 2>&1; \ +} 4>&1 || exit 1 + +# Check "--numeric" "--bytes" "--line-mode". +seq -w 3 1 100 \ +| { runWithValgrind -bnl -i 0.2 -f -L 100 2>"${workFile1}" > "${workFile2}"; } 4>&1 || exit 1 + +exit 0 + +# EOF diff --git a/tests/Memory_safety_-_Remote_control_receiver.sh b/tests/Memory_safety_-_Remote_control_receiver.sh new file mode 100644 index 0000000..37196bc --- /dev/null +++ b/tests/Memory_safety_-_Remote_control_receiver.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# +# Run valgrind's memory checker while receiving remote control commands. + +# Dummy assignments for "shellcheck". +sourcePath="${sourcePath:-.}"; testSubject="${testSubject:-false}"; workFile1="${workFile1:-.tmp1}"; workFile2="${workFile2:-.tmp2}"; workFile3="${workFile3:-.tmp3}"; workFile4="${workFile4:-.tmp4}" + +# Do nothing if IPC is not supported. +if ! "${testSubject}" -h 2>/dev/null | grep -Eq "^ -R,"; then + echo "IPC is not supported on this platform" + exit 2 +fi + +# Load the valgrind function. +. "${sourcePath}/autoconf/scripts/run-valgrind.sh" + +dd if=/dev/urandom of="${workFile1}" bs=1024 count=10240 2>/dev/null + +# Check from the POV of the process being controlled. +true > "${workFile3}" +( +set +e +while ! test -s "${workFile3}"; do usleep 200000 2>/dev/null || sleep 1; done +for loopCount in 1 2 3; do + "${testSubject}" -R "$(cat "${workFile3}")" -apterb || exit 1 + (usleep 200000 || sleep 1) 2>/dev/null + "${testSubject}" -R "$(cat "${workFile3}")" -p || exit 1 + (usleep 200000 || sleep 1) 2>/dev/null + "${testSubject}" -R "$(cat "${workFile3}")" -N "test" || exit 1 + (usleep 200000 || sleep 1) 2>/dev/null + "${testSubject}" -R "$(cat "${workFile3}")" -F "%e" || exit 1 + (usleep 200000 || sleep 1) 2>/dev/null + "${testSubject}" -R "$(cat "${workFile3}")" -N "." || exit 1 + (usleep 200000 || sleep 1) 2>/dev/null + echo "${loopCount}" >/dev/null # dummy for shellcheck +done +"${testSubject}" -R "$(cat "${workFile3}")" -L 10M +) & +{ runWithValgrind -L 100k -i 0.1 -f -P "${workFile3}" "${workFile1}" > "${workFile2}" 2>/dev/null; } 4>&1 || exit 1 + +exit 0 + +# EOF diff --git a/tests/Memory_safety_-_Remote_control_sender.sh b/tests/Memory_safety_-_Remote_control_sender.sh new file mode 100644 index 0000000..1175a08 --- /dev/null +++ b/tests/Memory_safety_-_Remote_control_sender.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# +# Run valgrind's memory checker while using remote control to control +# another process. + +# Dummy assignments for "shellcheck". +sourcePath="${sourcePath:-.}"; testSubject="${testSubject:-false}"; workFile1="${workFile1:-.tmp1}"; workFile2="${workFile2:-.tmp2}"; workFile3="${workFile3:-.tmp3}"; workFile4="${workFile4:-.tmp4}" + +# Do nothing if IPC is not supported. +if ! "${testSubject}" -h 2>/dev/null | grep -Eq "^ -R,"; then + echo "IPC is not supported on this platform" + exit 2 +fi + +# Load the valgrind function. +. "${sourcePath}/autoconf/scripts/run-valgrind.sh" + +dd if=/dev/urandom of="${workFile1}" bs=1024 count=10240 2>/dev/null + +# Check from the POV of the process sending a control command. +true > "${workFile3}" +"${testSubject}" -L 100k -i 0.1 -f -P "${workFile3}" "${workFile1}" > "${workFile2}" 2>/dev/null & +while ! test -s "${workFile3}"; do usleep 200000 2>/dev/null || sleep 1; done +runWithValgrind -R "$(cat "${workFile3}")" -apterb 4>&1 || exit 1 +(usleep 200000 || sleep 1) 2>/dev/null +runWithValgrind -R "$(cat "${workFile3}")" -p 4>&1 || exit 1 +(usleep 200000 || sleep 1) 2>/dev/null +runWithValgrind -R "$(cat "${workFile3}")" -N "test" 4>&1 || exit 1 +(usleep 200000 || sleep 1) 2>/dev/null +runWithValgrind -R "$(cat "${workFile3}")" -F "%e" 4>&1 || exit 1 +(usleep 200000 || sleep 1) 2>/dev/null +runWithValgrind -R "$(cat "${workFile3}")" -N "." 4>&1 || exit 1 +(usleep 200000 || sleep 1) 2>/dev/null +runWithValgrind -R "$(cat "${workFile3}")" -L 10M 4>&1 || exit 1 +wait + +exit 0 + +# EOF