diff --git a/Makefile b/Makefile index bb044e4..57ab2dd 100644 --- a/Makefile +++ b/Makefile @@ -59,3 +59,7 @@ example-programs-build/write-EBADF: example-programs/write-EBADF.c example-programs-build/access-itself: example-programs/access-itself.c mkdir -p example-programs-build gcc -static -std=c99 -Wall -Werror example-programs/access-itself.c -o example-programs-build/access-itself + +example-programs-build/connect: example-programs/connect.c + mkdir -p example-programs-build + gcc -static -std=c99 -Wall -Werror example-programs/connect.c -o example-programs-build/connect diff --git a/example-programs/connect.c b/example-programs/connect.c new file mode 100644 index 0000000..736718b --- /dev/null +++ b/example-programs/connect.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void die_usage(void){ + fprintf(stderr, "Usage: connect [FLAG]\n"); + exit(1); +} + +void error_connect(const char* str){ + int err = errno; + printf("\n Error: Could not connect to %s socket: %s \n", str, strerror(err)); + exit(1); +} + +void connectInet6(){ + int sockfd = socket(AF_INET6, SOCK_STREAM, 0); + if(sockfd < 0){ + printf("\n Error : Could not create Inet6 socket \n"); + exit(1); + } + struct sockaddr_in6 addr; + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(5000); + inet_pton(AF_INET6, "::1", &addr.sin6_addr); + if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0){ + // fail silently, since noone is listening. + } + close(sockfd); +} + + +void connectInet(){ + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if(sockfd < 0){ + printf("\n Error : Could not create Inet socket \n"); + exit(1); + } + struct sockaddr_in addr; + addr.sin_family = AF_INET; + if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0){ + // fail silently, since noone is listening. + } + close(sockfd); +} + +void connectNetlink(){ + int sockfd = socket(PF_NETLINK, SOCK_RAW, 0); + if(sockfd < 0){ + printf("\n Error: Could not create netlink socket \n"); + exit(1); + } + struct sockaddr_nl addr; + addr.nl_family = AF_NETLINK; + addr.nl_pid = getpid(); /* self pid */ + if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0){ + // fail silently, since noone is listening. + } + close(sockfd); +} + +int listenUnixSocket(){ + int sockfd = socket(PF_UNIX, SOCK_STREAM, 0); + if(sockfd < 0){ + printf("\n Error: Could not create unix socket \n"); + exit(1); + } + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, UNIX_PATH_MAX, "./test_socket"); + if(unlink(addr.sun_path) != 0 && errno != ENOENT){ + printf("\n Failed to unlink."); + exit(1); + } + if(bind(sockfd, (struct sockaddr*) &addr, sizeof(addr)) != 0){ + printf("\n Failed to bind to Unix socket."); + exit(1); + } + if(listen(sockfd, 5) != 0){ + printf("\n Failed to listen to socket file descriptor."); + exit(1); + } + return sockfd; +} + +void connectUnix(){ + int sockfd = socket(PF_UNIX, SOCK_STREAM, 0); + if(sockfd < 0){ + printf("\n Error: Could not create unix socket \n"); + exit(1); + } + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, UNIX_PATH_MAX, "./test_socket"); + int listenfd = listenUnixSocket(); + if(connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) != 0){ + error_connect("unix"); + } + if(close(listenfd) != 0 || close(sockfd) != 0){ + printf("\n Failed to close Unix socket."); + exit(1); + } +} + +void connectPacket(){ + /* TODO: implement connect call for packet socket */ +} + +int main(int argc, char const *argv[]){ + if (argc != 2){ + die_usage(); + } + int i = atoi(argv[1]); + switch(i){ + case AF_UNIX: + connectUnix(); + break; + case AF_INET: + connectInet(); + break; + case AF_INET6: + connectInet6(); + break; + case AF_PACKET: + connectPacket(); + break; + case AF_NETLINK: + connectNetlink(); + break; + default: + die_usage(); + } + /* code */ + return 0; +} diff --git a/hatrace.cabal b/hatrace.cabal index da1d304..3743bf2 100644 --- a/hatrace.cabal +++ b/hatrace.cabal @@ -48,6 +48,7 @@ library , unix , unliftio , unliftio-core + , wide-word ghc-options: -Wall default-language: Haskell2010 diff --git a/src/System/Hatrace.hs b/src/System/Hatrace.hs index 6775422..821d172 100644 --- a/src/System/Hatrace.hs +++ b/src/System/Hatrace.hs @@ -49,6 +49,8 @@ module System.Hatrace , SyscallExitDetails_exit(..) , SyscallEnterDetails_exit_group(..) , SyscallExitDetails_exit_group(..) + , SyscallEnterDetails_connect(..) + , SyscallExitDetails_connect(..) , DetailedSyscallEnter(..) , DetailedSyscallExit(..) , ERRNO(..) @@ -92,7 +94,7 @@ import qualified Data.Text.Encoding as T import Data.Word (Word32, Word64) import Foreign.C.Error (Errno(..), throwErrnoIfMinus1, throwErrnoIfMinus1_, getErrno, resetErrno, eCHILD, eINVAL) import Foreign.C.String (peekCString) -import Foreign.C.Types (CInt(..), CLong(..), CULong(..), CChar(..), CSize(..)) +import Foreign.C.Types (CInt(..), CUInt(..), CLong(..), CULong(..), CChar(..), CSize(..)) import Foreign.ForeignPtr (withForeignPtr) import Foreign.Marshal.Alloc (alloca) import Foreign.Marshal.Array (withArray) @@ -107,6 +109,7 @@ import System.IO.Error (modifyIOError, ioeGetLocation, ioeSetLocation) import System.Linux.Ptrace (TracedProcess(..), peekBytes, peekNullTerminatedBytes, peekNullWordTerminatedWords, detach) import System.Linux.Ptrace.Syscall hiding (ptrace_syscall, ptrace_detach) import qualified System.Linux.Ptrace.Syscall as Ptrace.Syscall +import qualified System.Linux.Ptrace as Ptrace import System.Linux.Ptrace.Types (Regs(..)) import System.Linux.Ptrace.X86_64Regs (X86_64Regs(..)) import System.Linux.Ptrace.X86Regs (X86Regs(..)) @@ -122,7 +125,6 @@ import UnliftIO.IORef (newIORef, writeIORef, readIORef) import System.Hatrace.SyscallTables.Generated (KnownSyscall(..), syscallName, syscallMap_i386, syscallMap_x64_64) import System.Hatrace.Types - mapLeft :: (a1 -> a2) -> Either a1 b -> Either a2 b mapLeft f = either (Left . f) Right @@ -541,6 +543,19 @@ data SyscallExitDetails_faccessat = SyscallExitDetails_faccessat { enterDetail :: SyscallEnterDetails_faccessat } deriving (Eq, Ord, Show) +data SyscallEnterDetails_connect = SyscallEnterDetails_connect + { sockfd :: CInt + , addr :: Ptr Void + , addrlen :: CUInt + -- Peeked details + , sockAddr :: SockAddr + } deriving (Eq, Ord, Show) + + +data SyscallExitDetails_connect = SyscallExitDetails_connect + { enterDetail :: SyscallEnterDetails_connect + , sockfd :: CInt + } deriving (Eq, Ord, Show) data SyscallEnterDetails_execve = SyscallEnterDetails_execve { filename :: Ptr CChar @@ -566,6 +581,7 @@ data DetailedSyscallEnter | DetailedSyscallEnter_pipe SyscallEnterDetails_pipe | DetailedSyscallEnter_pipe2 SyscallEnterDetails_pipe2 | DetailedSyscallEnter_access SyscallEnterDetails_access + | DetailedSyscallEnter_connect SyscallEnterDetails_connect | DetailedSyscallEnter_faccessat SyscallEnterDetails_faccessat | DetailedSyscallEnter_write SyscallEnterDetails_write | DetailedSyscallEnter_read SyscallEnterDetails_read @@ -587,6 +603,7 @@ data DetailedSyscallExit | DetailedSyscallExit_pipe SyscallExitDetails_pipe | DetailedSyscallExit_pipe2 SyscallExitDetails_pipe2 | DetailedSyscallExit_access SyscallExitDetails_access + | DetailedSyscallExit_connect SyscallExitDetails_connect | DetailedSyscallExit_faccessat SyscallExitDetails_faccessat | DetailedSyscallExit_write SyscallExitDetails_write | DetailedSyscallExit_read SyscallExitDetails_read @@ -772,6 +789,16 @@ getSyscallEnterDetails syscall syscallArgs pid = let proc = TracedProcess pid in Syscall_exit -> do let SyscallArgs{ arg0 = status } = syscallArgs pure $ DetailedSyscallEnter_exit $ SyscallEnterDetails_exit { status = fromIntegral status } + Syscall_connect -> do + let SyscallArgs{ arg0 = sockfd, arg1 = addr, arg2 = addrLen} = syscallArgs + let addrPtr = word64ToPtr addr + sockAddr <- wrapPeekVariableLength (TracedProcess pid) (word64ToPtr addr) addrLen peekSockAddr + pure $ DetailedSyscallEnter_connect $ SyscallEnterDetails_connect + { sockfd = fromIntegral sockfd + , addr = word64ToPtr addr + , addrlen = fromIntegral addrLen + , sockAddr = sockAddr + } Syscall_exit_group -> do let SyscallArgs{ arg0 = status } = syscallArgs pure $ DetailedSyscallEnter_exit_group $ SyscallEnterDetails_exit_group { status = fromIntegral status } @@ -886,6 +913,10 @@ getSyscallExitDetails knownSyscall syscallArgs pid = do enterDetail@SyscallEnterDetails_exit_group{} -> do pure $ DetailedSyscallExit_exit_group $ SyscallExitDetails_exit_group { enterDetail } + DetailedSyscallEnter_connect + enterDetail@SyscallEnterDetails_connect{} -> do + pure $ DetailedSyscallExit_connect $ SyscallExitDetails_connect { enterDetail, sockfd = fromIntegral result } + DetailedSyscallEnter_unimplemented syscall _syscallArgs -> pure $ DetailedSyscallExit_unimplemented syscall syscallArgs result @@ -983,6 +1014,10 @@ formatDetailedSyscallEnter = \case SyscallEnterDetails_exit_group{ status } -> "exit_group(" ++ show status ++ ")" + DetailedSyscallEnter_connect + SyscallEnterDetails_connect {sockfd, sockAddr, addrlen} -> + "connect(" ++ show sockfd ++ ", " ++ show sockAddr ++ ", " ++ show addrlen ++ ")" + DetailedSyscallEnter_unimplemented syscall syscallArgs -> "unimplemented_syscall_details(" ++ show syscall ++ ", " ++ show syscallArgs ++ ")" @@ -1071,6 +1106,10 @@ formatDetailedSyscallExit = \case SyscallExitDetails_exit_group{ enterDetail = SyscallEnterDetails_exit_group{ status }} -> "exit_group(" ++ show status ++ ")" + DetailedSyscallExit_connect + SyscallExitDetails_connect { enterDetail = SyscallEnterDetails_connect{sockfd, sockAddr, addrlen}} -> + "connect(" ++ show sockfd ++ show sockAddr ++ ", " ++ show addrlen ++ ") = " ++ show sockfd + DetailedSyscallExit_unimplemented syscall syscallArgs result -> "unimplemented_syscall_details(" ++ show syscall ++ ", " ++ show syscallArgs ++ ") = " ++ show result diff --git a/src/System/Hatrace/Types.hsc b/src/System/Hatrace/Types.hsc index 149688d..676f763 100644 --- a/src/System/Hatrace/Types.hsc +++ b/src/System/Hatrace/Types.hsc @@ -1,4 +1,13 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE ScopedTypeVariables #-} + #include +#include +#include +#include +#include +#include module System.Hatrace.Types ( FileAccessMode(..) @@ -6,11 +15,28 @@ module System.Hatrace.Types , fileExistence , CIntRepresentable(..) , HatraceShow(..) + , SockAddr(..) + , UnixSockAddr(..) + , InetSockAddr(..) + , Inet6SockAddr(..) + , NetlinkSockAddr(..) + , PacketSockAddr(..) + , wrapPeekVariableLength + , peekSockAddr + , AF(..) + , afToInt ) where import Data.Bits +import qualified Data.ByteString as BS +import Data.ByteString.Unsafe (unsafeUseAsCStringLen) import Data.List (intercalate) -import Foreign.C.Types (CInt(..)) +import Data.Word (Word64) +import Foreign.C.Types (CInt(..), CUShort(..), CUInt(..), CULong(..), CChar, CUChar) +import Foreign.Storable (Storable(..)) +import Foreign.Ptr +import System.Linux.Ptrace (TracedProcess(..), peekBytes) +import Data.WideWord.Word128 -- | Helper type class for int-sized enum-like types class CIntRepresentable a where @@ -65,3 +91,197 @@ instance CIntRepresentable FileAccessMode where } where accessBits = (#const R_OK) .|. (#const W_OK) .|. (#const X_OK) + + +data SockAddr + = SockAddrUnix UnixSockAddr + | SockAddrInet InetSockAddr + | SockAddrInet6 Inet6SockAddr + | SockAddrNetlink NetlinkSockAddr + | SockAddrPacket PacketSockAddr + | SockAddrUnsupportedFamily UnsupportedFamilySockAddr + deriving (Eq, Ord, Show) + +data UnixSockAddr = UnixSockAddr + { sun_family :: !CUShort -- ^ should always be AF_UNIX + , sun_path :: !BS.ByteString + } + deriving (Eq, Ord, Show) + +data InetSockAddr = InetSockAddr + { sin_family :: !CUShort -- ^ should always be AF_INET + , sin_port :: !CUShort -- ^ port number + , sin_addr :: !InetAddr -- ^ IPv4 address + } + deriving (Eq, Ord, Show) + +data Inet6SockAddr = Inet6SockAddr + { sin6_family :: !CUShort -- ^ should always be AF_INET6 + , sin6_port :: !CUShort -- ^ port number + , sin6_flowinfo :: !CULong -- ^ IPv6 flow information + , sin6_addr :: !Inet6Addr -- ^ IPv6 address + , sin6_scope_id :: !CUInt -- ^ Scope ID + } + deriving (Eq, Ord, Show) + +data NetlinkSockAddr = NetlinkSockAddr + { nl_family :: !CUShort -- ^ should always be AF_NETLINK + , nl_pad :: !CUShort -- ^ Should be Zero + , nl_pid :: !CInt -- ^ Port ID + , nl_groups :: !CUInt -- ^ Multicast groups mask + } + deriving (Eq, Ord, Show) + +data PacketSockAddr = PacketSockAddr + { sll_family :: !CUShort -- ^ should always be AF_PACKET + , sll_protocol :: !CUShort -- ^ Physical-layer protocol + , sll_ifindex :: !Int -- ^ Interface number + , sll_hatype :: !CUShort -- ^ ARP hardware type + , sll_pkttype :: !CUChar -- ^ Packet type + , sll_halen :: !CUChar -- ^ Length of address + , sll_addr :: !Word64 -- ^ Physical-layer address + } + deriving (Eq, Ord, Show) + +data UnsupportedFamilySockAddr = UnsupportedFamilySockAddr + { sa_family :: !CUShort + } + deriving (Eq, Ord, Show) + +data InetAddr = InetAddr { s_addr :: CUInt } + deriving (Eq, Ord, Show) + +data Inet6Addr = Inet6Addr { s6_addr :: Word128 } -- IPv6 address (16 bytes) + deriving (Eq, Ord, Show) + +data AF = AF_UNIX + | AF_INET + | AF_INET6 + | AF_NETLINK + | AF_PACKET + deriving (Eq, Ord, Show) + +afToInt :: AF -> Int +afToInt AF_UNIX = (#const AF_UNIX) +afToInt AF_INET = (#const AF_INET) +afToInt AF_INET6 = (#const AF_INET6) +afToInt AF_NETLINK = (#const AF_NETLINK) +afToInt AF_PACKET = (#const AF_PACKET) + +wrapPeekVariableLength :: TracedProcess -> Ptr a -> Word64 -> (Ptr CChar -> Word64 -> IO b) -> IO b +wrapPeekVariableLength process remotePtr numBytes f = do + bytes <- peekBytes process remotePtr (fromIntegral numBytes) + unsafeUseAsCStringLen bytes (\(ptr, len) -> f ptr (fromIntegral len)) + +-- TODO: check types with Template Haskell + +peekSockAddr :: Ptr CChar -> Word64 -> IO SockAddr +peekSockAddr ptr addrSize = do + (f :: CUShort) <- #{peek struct sockaddr, sa_family} ptr + case f of + (#const AF_UNIX) -> SockAddrUnix <$> peekUnixSockAddr ptr addrSize + (#const AF_INET) -> SockAddrInet <$> peekInetSockAddr ptr + (#const AF_INET6) -> SockAddrInet6 <$> peekInet6SockAddr ptr + (#const AF_NETLINK) -> SockAddrNetlink <$> peekNetlinkSockAddr ptr + (#const AF_PACKET) -> SockAddrPacket <$> peekPacketSockAddr ptr + _ -> SockAddrUnsupportedFamily <$> return UnsupportedFamilySockAddr {sa_family = f} + + +peekUnixSockAddr :: Ptr CChar -> Word64 -> IO UnixSockAddr +peekUnixSockAddr p addrSize = do + family <- #{peek struct sockaddr_un, sun_family} p + case addrSize of + #{size sa_family_t} -> return UnixSockAddr { + sun_family = family, + sun_path = "" + } + _ -> do + let pathPtr = #{ptr struct sockaddr_un, sun_path} p :: Ptr CChar + let pathSize = addrSize - #{size sa_family_t} :: Word64 + path <- BS.packCStringLen (pathPtr, fromIntegral pathSize) + return UnixSockAddr { + sun_family = family, + sun_path = path + } + + +peekInetSockAddr :: Ptr CChar -> IO InetSockAddr +peekInetSockAddr ptr = do + family <- #{peek struct sockaddr_in, sin_family} ptr + port <- #{peek struct sockaddr_in, sin_port} ptr + addr <- #{peek struct sockaddr_in, sin_addr} ptr + return InetSockAddr { + sin_family = family, + sin_port = port, + sin_addr = addr + } + + +peekNetlinkSockAddr :: Ptr CChar -> IO NetlinkSockAddr +peekNetlinkSockAddr ptr = do + family <- #{peek struct sockaddr_nl, nl_family} ptr + pad <- #{peek struct sockaddr_nl, nl_pad} ptr + pidNl <- #{peek struct sockaddr_nl, nl_pid} ptr + groups <- #{peek struct sockaddr_nl, nl_groups} ptr + return NetlinkSockAddr { + nl_family = family, + nl_pad = pad, + nl_pid = pidNl, + nl_groups = groups + } + + +peekInet6SockAddr :: Ptr CChar -> IO Inet6SockAddr +peekInet6SockAddr ptr = do + family <- #{peek struct sockaddr_in6, sin6_family} ptr + port <- #{peek struct sockaddr_in6, sin6_port} ptr + flowinfo <- #{peek struct sockaddr_in6, sin6_flowinfo} ptr + addr <- #{peek struct sockaddr_in6, sin6_addr} ptr + scopeId <- #{peek struct sockaddr_in6, sin6_scope_id} ptr + return $ Inet6SockAddr { + sin6_family = family, + sin6_port = port, + sin6_flowinfo = flowinfo, + sin6_addr = addr, + sin6_scope_id = scopeId + } + + +peekPacketSockAddr :: Ptr CChar -> IO PacketSockAddr +peekPacketSockAddr ptr = do + family <- #{peek struct sockaddr_ll, sll_family} ptr + protocol <- #{peek struct sockaddr_ll, sll_protocol} ptr + ifindex <- #{peek struct sockaddr_ll, sll_ifindex} ptr + hatype <- #{peek struct sockaddr_ll, sll_hatype} ptr + pkttype <- #{peek struct sockaddr_ll, sll_pkttype} ptr + halen <- #{peek struct sockaddr_ll, sll_halen} ptr + addr <- #{peek struct sockaddr_ll, sll_addr} ptr + return PacketSockAddr { + sll_family = family, + sll_protocol = protocol, + sll_ifindex = ifindex, + sll_hatype = hatype, + sll_pkttype = pkttype, + sll_halen = halen, + sll_addr = addr + } + + +instance Storable Inet6Addr where + sizeOf _ = #{size struct in6_addr} + alignment _ = #{alignment struct in6_addr} + peek ptr = do + addr <- #{peek struct in6_addr, s6_addr} ptr + return $ Inet6Addr { s6_addr = addr } + poke ptr (Inet6Addr addr) = do + #{poke struct in6_addr, s6_addr} ptr addr + + +instance Storable InetAddr where + sizeOf _ = #{size struct in_addr} + alignment _ = #{alignment struct in_addr} + peek ptr = do + addr <- #{peek struct in_addr, s_addr} ptr + return $ InetAddr { s_addr = addr} + poke ptr (InetAddr addr) = do + #{poke struct in_addr, s_addr} ptr addr diff --git a/test/HatraceSpec.hs b/test/HatraceSpec.hs index 45236e2..95b3ef7 100644 --- a/test/HatraceSpec.hs +++ b/test/HatraceSpec.hs @@ -5,7 +5,7 @@ module HatraceSpec where -import Control.Monad (when) +import Control.Monad (when, forM_) import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Unlift (MonadUnliftIO) import qualified Data.ByteString as BS @@ -31,6 +31,7 @@ import Text.Read (readMaybe) import UnliftIO.Exception (bracket) import System.Hatrace +import System.Hatrace.Types -- | Assertion we run before each test to ensure no leftover child processes @@ -549,3 +550,37 @@ spec = before_ assertNoChildren $ do ] x_OK = 1 accessModesRequested `shouldBe` [x_OK] + + describe "connect" $ do + it "seen when invoked in a program" $ do + let af = show . afToInt + let connect = "example-programs-build/connect" + callProcess "make" ["--quiet", connect] + forM_ [AF_UNIX, AF_INET, AF_INET6, AF_NETLINK] $ \code -> do + argv <- procToArgv connect [af code] + (exitCode, events) <- + sourceTraceForkExecvFullPathWithSink argv $ + syscallEnterDetailsOnlyConduit .| CL.consume + exitCode `shouldBe` ExitSuccess + let connectEnterData = + [ addr + | (_pid + , (DetailedSyscallEnter_connect + SyscallEnterDetails_connect + { sockAddr = addr }) + ) <- events + ] + length connectEnterData `shouldBe` 1 + let addr:_ = connectEnterData + case (code, addr) of + (AF_UNIX, SockAddrUnix sa) -> do + sun_family sa `shouldBe` fromIntegral (afToInt AF_UNIX) + (AF_INET, SockAddrInet sa) -> do + sin_family sa `shouldBe` fromIntegral (afToInt AF_INET) + (AF_INET6, SockAddrInet6 sa) -> do + sin6_family sa `shouldBe` fromIntegral (afToInt AF_INET6) + (AF_NETLINK, SockAddrNetlink sa) -> do + nl_family sa `shouldBe` fromIntegral (afToInt AF_NETLINK) + (AF_PACKET, SockAddrPacket sa) -> do + sll_family sa `shouldBe` fromIntegral (afToInt AF_PACKET) + _ -> fail $ "Attribute 'family' doesn't match. Expected: " ++ show (af code) ++ "(" ++ show code ++")"