Skip to content

Commit 4d22a04

Browse files
committed
local socket: tighten access rights
1 parent d85b45b commit 4d22a04

8 files changed

Lines changed: 142 additions & 7 deletions

File tree

ouroboros-network/framework/lib/Ouroboros/Network/Snocket.hs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
{-# LANGUAGE RankNTypes #-}
1111
{-# LANGUAGE ScopedTypeVariables #-}
1212
{-# LANGUAGE StandaloneDeriving #-}
13+
{-# LANGUAGE TypeApplications #-}
1314
{-# LANGUAGE TypeSynonymInstances #-}
1415

1516
#if !defined(mingw32_HOST_OS)
@@ -46,6 +47,7 @@ module Ouroboros.Network.Snocket
4647
, FileDescriptor
4748
, socketFileDescriptor
4849
, localSocketFileDescriptor
50+
, configureLocalSocketFileDescriptor
4951
-- ** for testing
5052
, invalidFileDescriptor
5153
-- * Re-exports
@@ -56,12 +58,12 @@ import Control.DeepSeq (NFData (..))
5658
import Control.Exception
5759
import Data.Bifoldable (Bifoldable (..))
5860
import Data.Bifunctor (Bifunctor (..))
61+
import Data.Bits
5962
import Data.Hashable
6063
import Data.Word
6164
import GHC.Generics (Generic)
6265
import Quiet (Quiet (..))
6366
#if defined(mingw32_HOST_OS)
64-
import Data.Bits
6567
import Foreign.Ptr (IntPtr (..), ptrToIntPtr)
6668
import System.Win32 qualified as Win32
6769
import System.Win32.Async qualified as Win32.Async
@@ -72,6 +74,10 @@ import "Win32" System.Win32.NamedPipes qualified as Win32.NamedPipes
7274
-- earlier version of Win32-network comes with `NamedPipes` APIs
7375
import "Win32-network" System.Win32.NamedPipes qualified as Win32.NamedPipes
7476
#endif
77+
#elif !defined(wasm32_HOST_ARCH)
78+
import Foreign.C (CInt)
79+
import System.Posix.Files qualified as Unix
80+
import System.Posix.Types qualified as Unix
7581
#endif
7682

7783
import NoThunks.Class
@@ -632,6 +638,34 @@ localSocketFileDescriptor =
632638
localSocketFileDescriptor = socketFileDescriptor . getLocalHandle
633639
#endif
634640

641+
-- | Restrict the local (Node-to-Client) socket so that only the owning user
642+
-- can connect.
643+
--
644+
-- On POSIX this sets mode @0600@ on the AF_UNIX socket. Both @fchmod@ on the
645+
-- socket file descriptor and @chmod@ on the bound path are called: the
646+
-- former is the canonical Linux/modern-macOS path; the latter is portability
647+
-- insurance for systems where @fchmod@ on a socket fd does not propagate to
648+
-- the on-disk inode.
649+
--
650+
-- On Windows this is a no-op: the local handle is a named pipe whose ACL is
651+
-- set at creation time and is not configurable via POSIX file modes.
652+
--
653+
-- On WASM this is a no-op: WASI does not implement the POSIX permission
654+
-- model in any meaningful way.
655+
--
656+
-- Must be called between 'Snocket.bind' (which creates the socket file) and
657+
-- 'Snocket.listen' (which makes connections possible).
658+
configureLocalSocketFileDescriptor :: LocalSocket -> LocalAddress -> IO ()
659+
#if defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH)
660+
configureLocalSocketFileDescriptor _ _ = return ()
661+
#else
662+
configureLocalSocketFileDescriptor sock (LocalAddress path) = do
663+
FileDescriptor fd <- localSocketFileDescriptor sock
664+
let mode = Unix.ownerReadMode .|. Unix.ownerWriteMode
665+
Unix.setFdMode (Unix.Fd (fromIntegral @Int @CInt fd)) mode
666+
Unix.setFileMode path mode
667+
#endif
668+
635669
-- | invalidFileDescriptor - when we need something for testing/simulation
636670
invalidFileDescriptor :: FileDescriptor
637671
invalidFileDescriptor = FileDescriptor (-1)

ouroboros-network/lib/Ouroboros/Network/Diffusion.hs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import Control.Concurrent.Class.MonadMVar (MonadMVar)
2525
import Control.Concurrent.Class.MonadSTM.Strict
2626
import Control.DeepSeq (NFData)
2727
import Control.Exception (IOException)
28+
#if !defined(mingw32_HOST_OS) && !defined(wasm32_HOST_ARCH)
29+
import Control.Monad (when)
30+
#endif
2831
import Control.Monad.Class.MonadAsync (Async, MonadAsync)
2932
import Control.Monad.Class.MonadAsync qualified as Async
3033
import Control.Monad.Class.MonadFork
@@ -33,6 +36,10 @@ import Control.Monad.Class.MonadTime.SI
3336
import Control.Monad.Class.MonadTimer.SI
3437
import Control.Monad.Fix (MonadFix)
3538
import Control.Tracer (Tracer, contramap, nullTracer, traceWith)
39+
#if !defined(mingw32_HOST_OS) && !defined(wasm32_HOST_ARCH)
40+
import Data.Bits ((.|.))
41+
import System.Posix.Files qualified as Unix
42+
#endif
3643
import Data.ByteString.Lazy (ByteString)
3744
import Data.Hashable (Hashable)
3845
import Data.IP qualified as IP
@@ -77,7 +84,8 @@ import Ouroboros.Network.PeerSharing (PeerSharingRegistry (..))
7784
import Ouroboros.Network.Protocol.Handshake
7885
import Ouroboros.Network.RethrowPolicy
7986
import Ouroboros.Network.Server qualified as Server
80-
import Ouroboros.Network.Snocket (LocalAddress, LocalSocket (..), RemoteAddress,
87+
import Ouroboros.Network.Snocket (LocalAddress (..), LocalSocket (..),
88+
RemoteAddress, configureLocalSocketFileDescriptor,
8189
localSocketFileDescriptor, makeLocalBearer, makeSocketBearer')
8290
import Ouroboros.Network.Snocket qualified as Snocket
8391
import Ouroboros.Network.Socket (configureSocket, configureSystemdSocket)
@@ -164,6 +172,7 @@ runM Interfaces
164172
, diNtcSnocket
165173
, diNtcBearer
166174
, diNtcGetFileDescriptor
175+
, diNtcConfigureFileDescriptor
167176
, diRng
168177
, diDnsActions
169178
, diConnStateIdSupply
@@ -318,7 +327,7 @@ runM Interfaces
318327
mkLocalThread :: ThreadId m -> Either ntcFd ntcAddr -> m Void
319328
mkLocalThread mainThreadId localAddr = do
320329
labelThisThread "diffusion-local"
321-
withLocalSocket tracer diNtcGetFileDescriptor diNtcSnocket localAddr
330+
withLocalSocket tracer diNtcGetFileDescriptor diNtcConfigureFileDescriptor diNtcSnocket localAddr
322331
$ \localSocket -> do
323332
localInbInfoChannel <- newInformationChannel
324333

@@ -912,7 +921,7 @@ run extraParams tracers args apps = do
912921
--
913922

914923
mkInterfaces :: IOManager
915-
-> Tracer IO (DiffusionTracer ntnAddr ntcAddr)
924+
-> Tracer IO (DiffusionTracer ntnAddr LocalAddress)
916925
-> DiffTime
917926
-> IO (Interfaces Socket
918927
RemoteAddress
@@ -921,7 +930,6 @@ mkInterfaces :: IOManager
921930
Resolver
922931
IO)
923932
mkInterfaces iocp tracer egressPollInterval = do
924-
925933
diRng <- newStdGen
926934
diConnStateIdSupply <- atomically $ CM.newConnStateIdSupply Proxy
927935

@@ -941,11 +949,44 @@ mkInterfaces iocp tracer egressPollInterval = do
941949
diNtcSnocket = Snocket.localSnocket iocp,
942950
diNtcBearer = makeLocalBearer,
943951
diNtcGetFileDescriptor = localSocketFileDescriptor,
952+
diNtcConfigureFileDescriptor = \sock addr -> do
953+
configureLocalSocketFileDescriptor sock addr
954+
checkLocalSocketDirectory tracer addr,
944955
diDnsActions = RootPeersDNS.ioDNSActions,
945956
diRng,
946957
diConnStateIdSupply
947958
}
948959

960+
-- | Trace a warning if the parent directory of a local-socket path has
961+
-- @group@ or @other@ write permission. A @0600@ socket inside such a
962+
-- directory is still vulnerable to manipulation by another local user
963+
-- with write access to that directory.
964+
--
965+
-- No-op on Windows (named pipes do not live in a POSIX-permissioned
966+
-- directory) and on WASM (WASI does not implement the POSIX permission
967+
-- model in any meaningful way).
968+
checkLocalSocketDirectory
969+
:: Tracer IO (DiffusionTracer ntnAddr LocalAddress)
970+
-> LocalAddress
971+
-> IO ()
972+
#if defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH)
973+
checkLocalSocketDirectory _ _ = return ()
974+
#else
975+
checkLocalSocketDirectory tracer addr@(LocalAddress path) = do
976+
let dir = parentDirectory path
977+
st <- Unix.getFileStatus dir
978+
let mode = Unix.fileMode st
979+
writeMask = Unix.groupWriteMode .|. Unix.otherWriteMode
980+
when (Unix.intersectFileModes mode writeMask /= Unix.nullFileMode) $
981+
traceWith tracer (InsecureLocalSocketDirectory addr (fromIntegral mode))
982+
where
983+
parentDirectory :: FilePath -> FilePath
984+
parentDirectory p = case break (== '/') (reverse p) of
985+
(_, '/' : revParent) | not (null revParent) -> reverse revParent
986+
(_, '/' : _) -> "/"
987+
_ -> "."
988+
#endif
989+
949990
--
950991
-- Data flow
951992
--

ouroboros-network/lib/Ouroboros/Network/Diffusion/Types.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import Data.Maybe.Strict
6161
import Data.Set (Set)
6262
import Data.Typeable (Typeable)
6363
import Data.Void (Void)
64+
import Data.Word (Word32)
6465
import System.Random (StdGen)
6566

6667
import Network.Mux qualified as Mx
@@ -109,8 +110,15 @@ data DiffusionTracer ntnAddr ntcAddr
109110
| CreateSystemdSocketForSnocketPath ntcAddr
110111
| CreatedLocalSocket ntcAddr
111112
| ConfiguringLocalSocket ntcAddr FileDescriptor
113+
| ConfiguredLocalSocket ntcAddr FileDescriptor
112114
| ListeningLocalSocket ntcAddr FileDescriptor
113115
| LocalSocketUp ntcAddr FileDescriptor
116+
-- | The parent directory of the local socket has @group@ or @other@ write
117+
-- bits set. This is a warning, not a failure: the socket itself is
118+
-- mode @0600@, but the parent directory's permissions may still allow
119+
-- another local user to rename or unlink the socket file. The 'Word32'
120+
-- is the parent directory's POSIX mode.
121+
| InsecureLocalSocketDirectory ntcAddr Word32
114122
-- Rename as 'CreateServerSocket'
115123
| CreatingServerSocket ntnAddr
116124
| ConfiguringServerSocket ntnAddr
@@ -767,6 +775,9 @@ data Interfaces ntnFd ntnAddr ntcFd ntcAddr
767775
diNtcGetFileDescriptor
768776
:: ntcFd -> m FileDescriptor,
769777

778+
diNtcConfigureFileDescriptor
779+
:: ntcFd -> ntcAddr -> m (),
780+
770781
-- | diffusion pseudo random generator. It is split between various
771782
-- components that need randomness, e.g. inbound governor, peer
772783
-- selection, policies, etc.

ouroboros-network/lib/Ouroboros/Network/Diffusion/Utils.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,14 @@ withLocalSocket :: forall ntnAddr ntcFd ntcAddr m a.
8181
)
8282
=> Tracer m (DiffusionTracer ntnAddr ntcAddr)
8383
-> (ntcFd -> m FileDescriptor)
84+
-> (ntcFd -> ntcAddr -> m ())
85+
-- ^ configure the local socket file descriptor (e.g. set
86+
-- @0600@ permissions on POSIX)
8487
-> Snocket m ntcFd ntcAddr
8588
-> Either ntcFd ntcAddr
8689
-> (ntcFd -> m a)
8790
-> m a
88-
withLocalSocket tracer getFileDescriptor sn localAddress k =
91+
withLocalSocket tracer getFileDescriptor configureFileDescriptor sn localAddress k =
8992
bracket
9093
(
9194
case localAddress of
@@ -117,6 +120,9 @@ withLocalSocket tracer getFileDescriptor sn localAddress k =
117120
traceWith tracer . ConfiguringLocalSocket addr
118121
=<< getFileDescriptor sd
119122
Snocket.bind sn sd addr
123+
configureFileDescriptor sd addr
124+
traceWith tracer . ConfiguredLocalSocket addr
125+
=<< getFileDescriptor sd
120126
traceWith tracer . ListeningLocalSocket addr
121127
=<< getFileDescriptor sd
122128
Snocket.listen sn sd

ouroboros-network/orphan-instances/Ouroboros/Network/OrphanInstances.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,11 @@ instance (ToJSON localAddress, ToJSON remoteAddress) => ToJSON (DiffusionTracer
704704
, "address" .= localAddress
705705
, "socket" .= String (pack (show socket))
706706
]
707+
toJSON (ConfiguredLocalSocket localAddress socket) = object
708+
[ "kind" .= String "ConfiguredLocalSocket"
709+
, "address" .= localAddress
710+
, "socket" .= String (pack (show socket))
711+
]
707712
toJSON (ListeningLocalSocket localAddress socket) = object
708713
[ "kind" .= String "ListeningLocalSocket"
709714
, "address" .= localAddress
@@ -714,6 +719,11 @@ instance (ToJSON localAddress, ToJSON remoteAddress) => ToJSON (DiffusionTracer
714719
, "address" .= localAddress
715720
, "socket" .= String (pack (show fd))
716721
]
722+
toJSON (InsecureLocalSocketDirectory localAddress mode) = object
723+
[ "kind" .= String "InsecureLocalSocketDirectory"
724+
, "address" .= localAddress
725+
, "mode" .= String (pack (show mode))
726+
]
717727
toJSON (CreatingServerSocket sockAddr) = object
718728
[ "kind" .= String "CreatingServerSocket"
719729
, "address" .= sockAddr

ouroboros-network/ouroboros-network.cabal

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ library
329329

330330
if !os(windows)
331331
build-depends:
332-
directory
332+
directory,
333+
unix,
333334

334335
library framework
335336
import: ghc-options
@@ -400,6 +401,9 @@ library framework
400401

401402
if os(windows)
402403
build-depends: Win32 >=2.5.4.1 && <3.0
404+
405+
if !os(windows)
406+
build-depends: unix
403407
hs-source-dirs: framework
404408

405409
library tests-lib

ouroboros-network/tests/lib/Test/Ouroboros/Network/Diffusion/Node.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ run blockGeneratorArgs ni na
279279
, Diffusion.diNtcSnocket = iNtcSnocket ni
280280
, Diffusion.diNtcBearer = iNtcBearer ni
281281
, Diffusion.diNtcGetFileDescriptor = \_ -> pure invalidFileDescriptor
282+
, Diffusion.diNtcConfigureFileDescriptor = \_ _ -> pure ()
282283
, Diffusion.diRng = diffStgGen
283284
, Diffusion.diDnsActions = \tracer lookupType toPeerAddr ->
284285
mockDNSActions

ouroboros-network/tracing/Ouroboros/Network/Tracing/PeerSelection.hs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ instance (Show ntnAddr, Show ntcAddr) =>
5454
, "path" .= String (pack . show $ localAddress)
5555
, "socket" .= String (pack (show socket))
5656
]
57+
forMachine _dtal (Diff.ConfiguredLocalSocket localAddress socket) = mconcat
58+
[ "kind" .= String "ConfiguredLocalSocket"
59+
, "path" .= String (pack . show $ localAddress)
60+
, "socket" .= String (pack (show socket))
61+
]
5762
forMachine _dtal (Diff.ListeningLocalSocket localAddress socket) = mconcat
5863
[ "kind" .= String "ListeningLocalSocket"
5964
, "path" .= String (pack . show $ localAddress)
@@ -64,6 +69,11 @@ instance (Show ntnAddr, Show ntcAddr) =>
6469
, "path" .= String (pack . show $ localAddress)
6570
, "socket" .= String (pack (show fd))
6671
]
72+
forMachine _dtal (Diff.InsecureLocalSocketDirectory localAddress mode) = mconcat
73+
[ "kind" .= String "InsecureLocalSocketDirectory"
74+
, "path" .= String (pack . show $ localAddress)
75+
, "mode" .= String (pack (show mode))
76+
]
6777
forMachine _dtal (Diff.CreatingServerSocket socket) = mconcat
6878
[ "kind" .= String "CreatingServerSocket"
6979
, "socket" .= String (pack (show socket))
@@ -109,10 +119,14 @@ instance MetaTrace (Diff.DiffusionTracer ntnAddr ntcAddr) where
109119
Namespace [] ["CreatedLocalSocket"]
110120
namespaceFor Diff.ConfiguringLocalSocket {} =
111121
Namespace [] ["ConfiguringLocalSocket"]
122+
namespaceFor Diff.ConfiguredLocalSocket {} =
123+
Namespace [] ["ConfiguredLocalSocket"]
112124
namespaceFor Diff.ListeningLocalSocket {} =
113125
Namespace [] ["ListeningLocalSocket"]
114126
namespaceFor Diff.LocalSocketUp {} =
115127
Namespace [] ["LocalSocketUp"]
128+
namespaceFor Diff.InsecureLocalSocketDirectory {} =
129+
Namespace [] ["InsecureLocalSocketDirectory"]
116130
namespaceFor Diff.CreatingServerSocket {} =
117131
Namespace [] ["CreatingServerSocket"]
118132
namespaceFor Diff.ListeningServerSocket {} =
@@ -136,8 +150,10 @@ instance MetaTrace (Diff.DiffusionTracer ntnAddr ntcAddr) where
136150
severityFor (Namespace _ ["CreateSystemdSocketForSnocketPath"]) _ = Just Info
137151
severityFor (Namespace _ ["CreatedLocalSocket"]) _ = Just Info
138152
severityFor (Namespace _ ["ConfiguringLocalSocket"]) _ = Just Info
153+
severityFor (Namespace _ ["ConfiguredLocalSocket"]) _ = Just Info
139154
severityFor (Namespace _ ["ListeningLocalSocket"]) _ = Just Info
140155
severityFor (Namespace _ ["LocalSocketUp"]) _ = Just Info
156+
severityFor (Namespace _ ["InsecureLocalSocketDirectory"]) _ = Just Warning
141157
severityFor (Namespace _ ["CreatingServerSocket"]) _ = Just Info
142158
severityFor (Namespace _ ["ListeningServerSocket"]) _ = Just Info
143159
severityFor (Namespace _ ["ServerSocketUp"]) _ = Just Info
@@ -160,10 +176,20 @@ instance MetaTrace (Diff.DiffusionTracer ntnAddr ntcAddr) where
160176
"CreatedLocalSocket"
161177
documentFor (Namespace _ ["ConfiguringLocalSocket"]) = Just
162178
"ConfiguringLocalSocket"
179+
documentFor (Namespace _ ["ConfiguredLocalSocket"]) = Just $ mconcat
180+
[ "ConfiguredLocalSocket: the local socket's permissions have been "
181+
, "restricted (POSIX: mode 0600)."
182+
]
163183
documentFor (Namespace _ ["ListeningLocalSocket"]) = Just
164184
"ListeningLocalSocket"
165185
documentFor (Namespace _ ["LocalSocketUp"]) = Just
166186
"LocalSocketUp"
187+
documentFor (Namespace _ ["InsecureLocalSocketDirectory"]) = Just $ mconcat
188+
[ "InsecureLocalSocketDirectory: the parent directory of the local "
189+
, "socket has group or other write permission, leaving the socket "
190+
, "vulnerable to manipulation by another local user with write access "
191+
, "to that directory."
192+
]
167193
documentFor (Namespace _ ["CreatingServerSocket"]) = Just
168194
"CreatingServerSocket"
169195
documentFor (Namespace _ ["ListeningServerSocket"]) = Just
@@ -189,8 +215,10 @@ instance MetaTrace (Diff.DiffusionTracer ntnAddr ntcAddr) where
189215
, Namespace [] ["CreateSystemdSocketForSnocketPath"]
190216
, Namespace [] ["CreatedLocalSocket"]
191217
, Namespace [] ["ConfiguringLocalSocket"]
218+
, Namespace [] ["ConfiguredLocalSocket"]
192219
, Namespace [] ["ListeningLocalSocket"]
193220
, Namespace [] ["LocalSocketUp"]
221+
, Namespace [] ["InsecureLocalSocketDirectory"]
194222
, Namespace [] ["CreatingServerSocket"]
195223
, Namespace [] ["ListeningServerSocket"]
196224
, Namespace [] ["ServerSocketUp"]

0 commit comments

Comments
 (0)