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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions example-programs/change-write-result.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

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);
}
}
15 changes: 15 additions & 0 deletions src/System/Hatrace.hs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ module System.Hatrace
, ERRNO(..)
, foreignErrnoToERRNO
, getSyscallEnterDetails
, setExitedSyscallResult
, syscallEnterDetailsOnlyConduit
, syscallExitDetailsOnlyConduit
, FileWriteEvent(..)
Expand Down Expand Up @@ -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

Expand Down
46 changes: 45 additions & 1 deletion test/HatraceSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down