From: Gentoo Prefix Subject: [PATCH] GHCi: resolve linker scripts against the C compiler sysroot GHC's RTS linker reads GNU ld scripts, but passes the first GROUP member straight to dlopen. Recognise scripts regardless of their library name and resolve their first absolute GROUP/INPUT member against the configured C compiler sysroot before handing it to the RTS. This avoids accidentally loading a host library. The sysroot is queried from the configured compiler; it is not assumed to equal EPREFIX. On prefix-guest, gcc sysroot is usually / and does not contain EPREFIX, this patch does nothing. Index: ghc-9.8.4/compiler/GHC/Linker/Loader.hs =================================================================== --- ghc-9.8.4.orig/compiler/GHC/Linker/Loader.hs +++ ghc-9.8.4/compiler/GHC/Linker/Loader.hs @@ -93,6 +93,7 @@ import GHC.Linker.Types import Control.Monad import qualified Data.Set as Set +import qualified Data.ByteString as BS import Data.Char (isSpace) import Data.IORef import Data.List (intercalate, isPrefixOf, nub, partition) @@ -501,7 +502,13 @@ preloadLib interp hsc_env lib_paths fram return pls DLLPath dll_path -> do - do maybe_errstr <- loadDLL interp dll_path + do +#if defined(CAN_LOAD_DLL) + dll' <- resolveLinkerScript logger dflags dll_path +#else + let dll' = dll_path +#endif + maybe_errstr <- loadDLL interp dll' case maybe_errstr of Right _ -> maybePutStrLn logger "done" Left mm -> preloadFailed mm lib_paths lib_spec @@ -1224,13 +1231,53 @@ restriction very easily. -} #if defined(CAN_LOAD_DLL) +-- The RTS linker passes the first GROUP/INPUT member of an ld script to +-- dlopen verbatim. Unlike GNU ld, dlopen does not apply the C compiler's +-- sysroot. Resolve scripts in that sysroot before trying the host's paths. +resolveLinkerScript :: Logger -> DynFlags -> FilePath -> IO FilePath +resolveLinkerScript logger dflags dll + | not (isAbsolute dll) = pure dll + | otherwise = do + out <- askLd logger dflags [Option "--print-sysroot"] + case lines out of + root:_ | isAbsolute root && normalise root /= "/" -> do + root' <- canonicalizePath root + dll' <- canonicalizePath dll + let relative = makeRelative root' dll' + if relative == ".." || "../" `isPrefixOf` relative + then pure dll + else do + content <- BS.readFile dll' + if BS.isPrefixOf (BS.pack [0x7f, 0x45, 0x4c, 0x46]) content + then pure dll' + else case scriptMember (map (toEnum . fromEnum) (BS.unpack content)) of + Just member | isAbsolute member -> do + let target = root' dropWhile (== '/') member + exists <- doesFileExist target + if exists then pure target else missing target + -- Do not pass an unrecognised script to the RTS: it could + -- load an unprefixed member from the host. + _ -> missing dll' + _ -> pure dll + where + -- Treat parentheses as token separators, including GROUP(/path). + scriptMember content = + case dropWhile (\w -> w /= "GROUP" && w /= "INPUT") $ + words (map (\c -> if c == '(' || c == ')' then ' ' else c) content) of + (_:member:_) -> Just member + _ -> Nothing + + missing path = throwGhcExceptionIO $ InstallationError $ + "Cannot resolve linker script in C compiler sysroot: " ++ path + -- we have already searched the filesystem; the strings passed to load_dyn -- can be passed directly to loadDLL. They are either fully-qualified -- ("/usr/lib/libfoo.so"), or unqualified ("libfoo.so"). In the latter case, -- loadDLL is going to search the system paths to find the library. load_dyn :: Interp -> HscEnv -> Bool -> FilePath -> IO (Maybe (RemotePtr LoadedDLL)) load_dyn interp hsc_env crash_early dll = do - r <- loadDLL interp dll + dll' <- resolveLinkerScript logger (hsc_dflags hsc_env) dll + r <- loadDLL interp dll' case r of Right loaded_dll -> pure (Just loaded_dll) Left err ->