diff --git a/Makefile b/Makefile index 9e04261..b84fee9 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/execve EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/execve-linux-null-envp EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/atomic-write EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/write-EBADF +EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/change-write-result EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/access-itself EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/trigger-time EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/get-fs diff --git a/example-programs/change-write-result.c b/example-programs/change-write-result.c new file mode 100644 index 0000000..016ef02 --- /dev/null +++ b/example-programs/change-write-result.c @@ -0,0 +1,19 @@ +#include +#include +#include +#include + +int main(int argc, char const *argv[]) +{ + int res = write(1, "hello", 5); + + // from man for exit(3): + // The exit() function causes normal process termination and the value + // of status & 0377 is returned to the parent + // so only the last 8 bits are actually used + if (res == -1) { + exit(errno); // the error is set as an exit code to validate expectations in test case + } else { + exit(res); + } +} diff --git a/src/System/Hatrace.hs b/src/System/Hatrace.hs index 92572dd..3814cc8 100644 --- a/src/System/Hatrace.hs +++ b/src/System/Hatrace.hs @@ -80,6 +80,7 @@ module System.Hatrace , ERRNO(..) , foreignErrnoToERRNO , getSyscallEnterDetails + , setExitedSyscallResult , syscallEnterDetailsOnlyConduit , syscallExitDetailsOnlyConduit , FileWriteEvent(..) @@ -2478,6 +2479,20 @@ getExitedSyscallResult cpid = do then (fromIntegral (-1 :: Int), Just $ ERRNO $ fromIntegral (-retVal)) else (retVal, Nothing) +-- | Changes result of current syscall. Should be called only on exit event. +-- For 32-bit architectures Word64 type is too large, so only the last 32 bits will be used. +setExitedSyscallResult :: CPid -> Either ERRNO Word64 -> IO () +setExitedSyscallResult cpid errorOrRetValue = do + let newRetValue = + case errorOrRetValue of + Right num -> num + Left (ERRNO errno) -> fromIntegral (-errno) + regs <- annotatePtrace "setExitedSyscallResult: ptrace_getregs" $ ptrace_getregs cpid + let newRegs = + case regs of + X86 r -> X86 r { eax = fromIntegral newRetValue } + X86_64 r -> X86_64 r { rax = newRetValue } + annotatePtrace "setExitedSyscallResult: ptrace_setregs" $ ptrace_setregs cpid newRegs foreign import ccall safe "kill" c_kill :: CPid -> Signal -> IO CInt diff --git a/test/HatraceSpec.hs b/test/HatraceSpec.hs index b15bca9..a661f24 100644 --- a/test/HatraceSpec.hs +++ b/test/HatraceSpec.hs @@ -18,7 +18,7 @@ import Data.Set (Set) import qualified Data.Set as Set import qualified Data.Text as T import qualified Data.Text.Encoding as T -import Foreign.C.Error (eBADF) +import Foreign.C.Error (Errno(..), eBADF, eCONNRESET) import Foreign.Ptr (nullPtr, plusPtr) import Foreign.Storable (sizeOf) import System.FilePath (takeFileName, takeDirectory) @@ -381,6 +381,50 @@ spec = before_ assertNoChildren $ do exitCode `shouldBe` ExitSuccess Map.lookup tmpFile writes `shouldBe` Just NonatomicWrite + describe "modifying syscalls" $ do + + let changeWriteSyscallResult errorOrRetValue = + awaitForever $ \(pid, exitOrErrno) -> do + case exitOrErrno of + Left _ -> pure () + Right syscallExit -> case syscallExit of + DetailedSyscallExit_write SyscallExitDetails_write{} -> do + liftIO $ setExitedSyscallResult pid errorOrRetValue + _ -> pure () + + it "can change syscall result to any error" $ do + let writeCall = "example-programs-build/change-write-result" + callProcess "make" ["--quiet", writeCall] + argv <- procToArgv writeCall [] + let injectedErrno@(Errno expectedReturn) = + eCONNRESET + -- we don't check events, as we're interested in the actual result of + -- the syscall, which should be changed and this change needs to be + -- visible in the traced program + (exitCode, _) <- + sourceTraceForkExecvFullPathWithSink argv $ + syscallExitDetailsOnlyConduit .| + changeWriteSyscallResult (Left $ foreignErrnoToERRNO injectedErrno) .| + CL.consume + exitCode `shouldBe` (ExitFailure $ fromIntegral expectedReturn) + + it "can change syscall result to any return value" $ do + let writeCall = "example-programs-build/change-write-result" + callProcess "make" ["--quiet", writeCall] + argv <- procToArgv writeCall [] + -- this value below should fit into 8 bits, as exit() does not return + -- more bits to the parent + let newRetValue = 67 + -- we don't check events, as we're interested in the actual result of + -- the syscall, which should be changed and this change needs to be + -- visible in the traced program + (exitCode, _) <- + sourceTraceForkExecvFullPathWithSink argv $ + syscallExitDetailsOnlyConduit .| + changeWriteSyscallResult (Right $ fromIntegral newRetValue) .| + CL.consume + exitCode `shouldBe` (ExitFailure newRetValue) + describe "per-syscall tests" $ do describe "read" $ do