@hackage landlock0.2.1.1

Haskell bindings for the Linux Landlock API

  • Installation

  • Tested Compilers

  • Dependencies (8)

  • Dependents (0)

  • Package Flags

      landlocked
       (on by default)

      Build the landlocked utility.

      werror
       (off by default)

      Turn compiler warnings into errors.

landlock-hs: Haskell bindings for the Linux Landlock API

This library exposes Haskell bindings for the Linux kernel Landlock API.

The Linux kernel Landlock API provides unprivileged access control. The goal of Landlock is to enable to restrict ambient rights (e.g. global filesystem access) for a set of processes. Because Landlock is a stackable LSM, it makes possible to create safe security sandboxes as new security layers in addition to the existing system-wide access-controls. This kind of sandbox is expected to help mitigate the security impact of bugs or unexpected/malicious behaviors in user space applications. Landlock empowers any process, including unprivileged ones, to securely restrict themselves.

For more information, see the Landlock homepage and its kernel documentation.

Example

Here's a simple example, allowing read-only access to the user's SSH public key, full access to tmp and the ability to read and execute everything in /usr:

import System.Landlock (
      AccessFsFlag(..)
    , OpenPathFlags(..)
    , RulesetAttr(..)
    , abiVersion
    , accessFsFlags
    , accessFsFlagIsReadOnly
    , defaultOpenPathFlags
    , isSupported
    , landlock
    , pathBeneath
    , withOpenPath
    )

import ReadmeUtils

-- These tests run with a "fake" pre-populated rootfs
-- All files and directories in it are created using the current user,
-- readable, writable and (in case of scripts) executable, so without
-- Landlock, there would be no permission errors.

main :: IO ()
main = withFakeRoot $ \root -> do
    -- Check whether Landlock is supported
    hasLandlock <- isSupported

    unless hasLandlock $
        fail "Landlock not supported"

    version <- abiVersion
    -- Find all FS access flags for the running kernel's ABI version
    allFlags <- lookupAccessFsFlags version
    let roFlags = filter accessFsFlagIsReadOnly allFlags

    let homeDir = root </> "home" </> "user"
        publicKey = homeDir </> ".ssh" </> "id_ed25519.pub"
        privateKey = homeDir </> ".ssh" </> "id_ed25519"
        tmpDir = root </> "tmp"

    -- Construct the sandbox, locking down everything by default
    landlock (RulesetAttr allFlags) [] [] $ \addRule -> do
        -- /tmp is fully accessible
        withOpenPath tmpDir defaultOpenPathFlags{ directory = True } $ \tmp ->
            addRule (pathBeneath tmp allFlags) []

        -- SSH public key is readable
        withOpenPath publicKey defaultOpenPathFlags $ \key ->
            addRule (pathBeneath key [AccessFsReadFile]) []

        -- (Real) /usr is fully accessible, but read-only (includes executable permissions)
        withOpenPath "/usr" defaultOpenPathFlags{ directory = True } $ \usr -> do
            addRule (pathBeneath usr roFlags) []

    -- Can create and write to files in tmp
    writeFile (tmpDir </> "program.log") "Success!"

    -- Can read SSH key
    _pubKey <- readFile publicKey

    -- Can execute things in /usr
    "Linux\n" <- readProcess ("/usr" </> "bin" </> "uname") ["-s"] ""

    -- Can't read SSH private key
    assertPermissionDenied $
        readFile privateKey

    -- Can't write to SSH public key
    assertPermissionDenied $
        withFile publicKey WriteMode $ \fd ->
            hPutStrLn fd "Oops, key gone!"

    -- Can't remove SSH public key
    assertPermissionDenied $
        removeFile publicKey

    -- Can't create files in homedir
    assertPermissionDenied $
        writeFile (homeDir </> "whoops.txt") "This file should not exist!"

    -- Can't read files from (real) /etc
    assertPermissionDenied $
        readFile ("/etc" </> "hosts")

  where
    lookupAccessFsFlags version = case lookup version accessFsFlags of
        -- In a production implementation, we'd lookup the "best matching" flag
        -- set, not require an exact match.
        Nothing -> fail "Unsupported ABI version"
        Just flags -> return flags

    assertPermissionDenied act = handleJust permissionDenied return $ do
        _ <- act
        fail "Expected permission error"
    permissionDenied e = if isPermissionError e then Just () else Nothing