packages feed

landlock 0.2.0.1 → 0.2.1.0

raw patch · 29 files changed

+2620/−891 lines, 29 filesdep +directorydep +optparse-applicativedep +quickcheck-classes-basedep −tasty-expected-failuredep ~basedep ~exceptionsdep ~unixsetup-changednew-component:exe:landlockedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: directory, optparse-applicative, quickcheck-classes-base, temporary

Dependencies removed: tasty-expected-failure

Dependency ranges changed: base, exceptions, unix

API changes (from Hackage documentation)

- System.Landlock: instance GHC.Classes.Eq System.Landlock.AddRuleFlag
- System.Landlock: instance GHC.Classes.Eq System.Landlock.CreateRulesetFlag
- System.Landlock: instance GHC.Classes.Eq System.Landlock.RestrictSelfFlag
- System.Landlock: instance GHC.Enum.Bounded System.Landlock.CreateRulesetFlag
- System.Landlock: instance GHC.Enum.Enum System.Landlock.CreateRulesetFlag
- System.Landlock: instance GHC.Show.Show System.Landlock.AddRuleFlag
- System.Landlock: instance GHC.Show.Show System.Landlock.CreateRulesetFlag
- System.Landlock: instance GHC.Show.Show System.Landlock.RestrictSelfFlag
+ System.Landlock: AccessFsRefer :: AccessFsFlag
+ System.Landlock: AccessFsTruncate :: AccessFsFlag
+ System.Landlock: getVersion :: Version -> Word
+ System.Landlock: version2 :: Version
+ System.Landlock: version3 :: Version
- System.Landlock: data AccessFsFlag
+ System.Landlock: data () => AccessFsFlag
- System.Landlock: data OpenPathFlags
+ System.Landlock: data () => OpenPathFlags
- System.Landlock: data Rule (a :: RuleType)
+ System.Landlock: data () => Rule (a :: RuleType)
- System.Landlock: data Version
+ System.Landlock: data () => Version

Files

CHANGELOG.md view
@@ -1,5 +1,29 @@ # Revision history for landlock +## 0.2.1.0 -- 2023-02-22++* Use vendored `linux/landlock.h` instead of system-provided header during+  build.++* Support Landlock ABI v2 and `LANDLOCK_ACCESS_FS_REFER` as part of it.++* Support Landlock ABI v3 and `LANDLOCK_ACCESS_FS_TRUNCATE` as part of it.++* Support GHC 9.4.2 / `base ^>=4.17`.++* Support `unix ^>=2.8`.++* Add a new executable,`landlocked`, which permits to run a command in+  a sandboxed environment.++* Add a flag, `landlocked`, which allows to not build the `landlocked`+  executable.++* Use `capi` foreign imports instead of `ccall` using the `CApiFFI` language+  extension.++* Add a Cabal flag, `werror`, to enable compiler `-Werror` and friends.+ ## 0.2.0.1 -- 2022-08-24  * Code-wise the same as version 0.2.0.0, but said version was incorrectly
+ README.lhs view
@@ -0,0 +1,118 @@+# 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](https://landlock.io/) and its+[kernel documentation](https://docs.kernel.org/userspace-api/landlock.html).++## 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`:++```haskell+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+```
README.md view
@@ -13,3 +13,106 @@  For more information, see the [Landlock homepage](https://landlock.io/) and its [kernel documentation](https://docs.kernel.org/userspace-api/landlock.html).++## 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`:++```haskell+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+```
Setup.hs view
@@ -1,2 +1,3 @@ import Distribution.Simple+ main = defaultMain
+ bin/landlocked.hs view
@@ -0,0 +1,180 @@+module Main (main) where++import Control.Applicative (many, (<**>))+import Control.Exception.Base (displayException, handleJust)+import Control.Monad (forM_, unless)+import Data.List (intercalate, sortOn)+import Data.Maybe (listToMaybe)+import Data.Ord (Down (Down))+import Data.Version (versionBranch)+import Options.Applicative+  ( ParserInfo,+    ReadM,+    action,+    execParser,+    footer,+    fullDesc,+    header,+    help,+    helper,+    hidden,+    info,+    infoOption,+    long,+    metavar,+    noIntersperse,+    option,+    progDesc,+    str,+    strArgument,+  )+import qualified Paths_landlock as Paths+import System.Exit (ExitCode (ExitFailure), exitWith)+import System.IO (hPutStrLn, stderr)+import System.IO.Error (isDoesNotExistError, isPermissionError)+import System.Landlock+  ( AccessFsFlag,+    RulesetAttr (..),+    Version,+    abiVersion,+    accessFsFlagIsReadOnly,+    accessFsFlags,+    defaultOpenPathFlags,+    getVersion,+    isSupported,+    landlock,+    pathBeneath,+    withOpenPath,+  )+import System.Posix.Process (executeFile)++main :: IO ()+main = do+  hasLandlock <- isSupported+  unless hasLandlock $ do+    hPutStrLn stderr "Landlock not supported on this system"+    exitWith (ExitFailure 2)++  version <- abiVersion++  (usedVersion, allFlags) <- case lookupAccessFsFlags version of+    Nothing -> fail $ "Unable to retrieve file-system access flags for Landlock ABI " ++ show (getVersion version)+    Just r -> return r+  let roFlags = filter accessFsFlagIsReadOnly allFlags++  args <- execParser (parser version usedVersion)++  landlock (RulesetAttr allFlags) [] [] $ \addRule -> do+    forM_ (argsROPaths args) $ \path ->+      withOpenPath path defaultOpenPathFlags $ \fd ->+        addRule (pathBeneath fd roFlags) []++    forM_ (argsRWPaths args) $ \path ->+      withOpenPath path defaultOpenPathFlags $ \fd ->+        addRule (pathBeneath fd allFlags) []++  handleJust permissionDenied handlePermissionDenied $+    handleJust notFound handleNotFound $ do+      _ <- executeFile (argsCommand args) True (argsArguments args) Nothing+      fail "executeFile returned"+  where+    notFound e = if isDoesNotExistError e then Just e else Nothing+    handleNotFound e = do+      hPutStrLn stderr $+        unlines+          [ "Failed to execute command: " ++ displayException e+          ]+      exitWith (ExitFailure 127)+    permissionDenied e = if isPermissionError e then Just e else Nothing+    handlePermissionDenied e = do+      hPutStrLn stderr $+        unlines+          [ "Failed to execute command: " ++ displayException e,+            "Hint: access to the binary, the interpreter or shared libraries may be denied."+          ]+      exitWith (ExitFailure 126)++data Args = Args+  { argsROPaths :: [FilePath],+    argsRWPaths :: [FilePath],+    argsCommand :: FilePath,+    argsArguments :: [String]+  }+  deriving (Show, Eq)++parser :: Version -> Version -> ParserInfo Args+parser version usedVersion =+  info+    (argsParser <**> versionFlag <**> helper)+    ( fullDesc+        <> noIntersperse+        <> progDesc "Execute a command in a sandboxed environment"+        <> header+          ( unwords+              [ "Use landlocked to run a program in a sandboxed environment,",+                "restricting access to system resources using the Linux",+                "Landlock API."+              ]+          )+        <> footer+          ( unwords+              [ "The command binary, its interpreter and any shared",+                "libraries must be accessible in the sandbox."+              ]+          )+    )+  where+    argsParser =+      Args+        <$> many+          ( option+              filePath+              ( long "ro"+                  <> metavar "PATH"+                  <> help "Allow read-only access to given file or directory"+                  <> action "default"+              )+          )+        <*> many+          ( option+              filePath+              ( long "rw"+                  <> metavar "PATH"+                  <> help "Allow write access to given file or directory"+                  <> action "default"+              )+          )+        <*> strArgument+          ( metavar "COMMAND"+              <> help "Command to spawn"+              <> action "command"+          )+        <*> many+          ( strArgument+              ( metavar "ARG"+                  <> help "Arguments to pass to spawned command"+              )+          )+    versionFlag =+      infoOption+        versionString+        ( long "version"+            <> help "Show version information"+            <> hidden+        )+    versionString =+      unlines+        [ "landlocked " ++ intercalate "." (map show (versionBranch Paths.version)),+          "System Landlock ABI version: " ++ show (getVersion version),+          "Using Landlock ABI version: " ++ show (getVersion usedVersion)+        ]++-- This is not really useful. It could be if 'ReaderM' were 'MonadIO'+filePath :: ReadM FilePath+filePath = str++lookupAccessFsFlags :: Version -> Maybe (Version, [AccessFsFlag])+lookupAccessFsFlags v =+  listToMaybe $+    sortOn (Down . fst) $+      filter (\(v', _) -> v' <= v) accessFsFlags
cbits/hs-landlock.c view
@@ -1,38 +1,38 @@-#define _GNU_SOURCE- #include <stddef.h> #include <unistd.h>-#include <linux/landlock.h> #include <sys/syscall.h> #include <sys/types.h>  #include <hs-psx.h>  #include "hs-landlock.h"+#include "linux/landlock.h" -#ifndef landlock_create_ruleset-long landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,-                             const size_t size,-                             const __u32 flags) {+long hs_landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,+                                const size_t size,+                                const __u32 flags) {+#ifdef landlock_create_ruleset+        return landlock_create_ruleset(attr, size, flags)+#else         return syscall(__NR_landlock_create_ruleset, attr, size, flags);-} #endif+} -#ifndef landlock_add_rule-long landlock_add_rule(const int ruleset_fd,-                       const enum landlock_rule_type rule_type,-                       const void *const rule_attr,-                       const __u32 flags) {+long hs_landlock_add_rule(const int ruleset_fd,+                          const enum landlock_rule_type rule_type,+                          const void *const rule_attr,+                          const __u32 flags) {+#ifdef landlock_add_rule+        return landlock_add_rule(ruleset_fd, rule_type, rule_attr, flags)+#else         return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags);-} #endif+} -#ifndef landlock_restrict_self-long landlock_restrict_self(const int ruleset_fd,-                            const __u32 flags) {+long hs_landlock_restrict_self(const int ruleset_fd,+                               const __u32 flags) {         return hs_psx_syscall3(__NR_landlock_restrict_self, ruleset_fd, flags, 0); }-#endif  int hs_landlock_prctl(int option,                       unsigned long arg2,
cbits/hs-landlock.h view
@@ -2,22 +2,23 @@ #define _HS_LANDLOCK_H_  #include <stddef.h>-#include <linux/landlock.h> #include <sys/types.h> +#include "linux/landlock.h"+ #ifdef __cplusplus extern "C" { #endif -long landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,-                             const size_t size,-                             const __u32 flags);-long landlock_add_rule(const int ruleset_fd,-                       const enum landlock_rule_type rule_type,-                       const void *const rule_attr,-                       const __u32 flags);-long landlock_restrict_self(const int ruleset_fd,-                            const __u32 flags);+long hs_landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,+                                const size_t size,+                                const __u32 flags);+long hs_landlock_add_rule(const int ruleset_fd,+                          const enum landlock_rule_type rule_type,+                          const void *const rule_attr,+                          const __u32 flags);+long hs_landlock_restrict_self(const int ruleset_fd,+                               const __u32 flags);  int hs_landlock_prctl(int option,                       unsigned long arg2,
+ cbits/linux/GPL-2.0 view
@@ -0,0 +1,359 @@+Valid-License-Identifier: GPL-2.0+Valid-License-Identifier: GPL-2.0-only+Valid-License-Identifier: GPL-2.0++Valid-License-Identifier: GPL-2.0-or-later+SPDX-URL: https://spdx.org/licenses/GPL-2.0.html+Usage-Guide:+  To use this license in source code, put one of the following SPDX+  tag/value pairs into a comment according to the placement+  guidelines in the licensing rules documentation.+  For 'GNU General Public License (GPL) version 2 only' use:+    SPDX-License-Identifier: GPL-2.0+  or+    SPDX-License-Identifier: GPL-2.0-only+  For 'GNU General Public License (GPL) version 2 or any later version' use:+    SPDX-License-Identifier: GPL-2.0++  or+    SPDX-License-Identifier: GPL-2.0-or-later+License-Text:++		    GNU GENERAL PUBLIC LICENSE+		       Version 2, June 1991++ Copyright (C) 1989, 1991 Free Software Foundation, Inc.+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++			    Preamble++  The licenses for most software are designed to take away your+freedom to share and change it.  By contrast, the GNU General Public+License is intended to guarantee your freedom to share and change free+software--to make sure the software is free for all its users.  This+General Public License applies to most of the Free Software+Foundation's software and to any other program whose authors commit to+using it.  (Some other Free Software Foundation software is covered by+the GNU Library General Public License instead.)  You can apply it to+your programs, too.++  When we speak of free software, we are referring to freedom, not+price.  Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+this service if you wish), that you receive source code or can get it+if you want it, that you can change the software or use pieces of it+in new free programs; and that you know you can do these things.++  To protect your rights, we need to make restrictions that forbid+anyone to deny you these rights or to ask you to surrender the rights.+These restrictions translate to certain responsibilities for you if you+distribute copies of the software, or if you modify it.++  For example, if you distribute copies of such a program, whether+gratis or for a fee, you must give the recipients all the rights that+you have.  You must make sure that they, too, receive or can get the+source code.  And you must show them these terms so they know their+rights.++  We protect your rights with two steps: (1) copyright the software, and+(2) offer you this license which gives you legal permission to copy,+distribute and/or modify the software.++  Also, for each author's protection and ours, we want to make certain+that everyone understands that there is no warranty for this free+software.  If the software is modified by someone else and passed on, we+want its recipients to know that what they have is not the original, so+that any problems introduced by others will not reflect on the original+authors' reputations.++  Finally, any free program is threatened constantly by software+patents.  We wish to avoid the danger that redistributors of a free+program will individually obtain patent licenses, in effect making the+program proprietary.  To prevent this, we have made it clear that any+patent must be licensed for everyone's free use or not licensed at all.++  The precise terms and conditions for copying, distribution and+modification follow.++		    GNU GENERAL PUBLIC LICENSE+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION++  0. This License applies to any program or other work which contains+a notice placed by the copyright holder saying it may be distributed+under the terms of this General Public License.  The "Program", below,+refers to any such program or work, and a "work based on the Program"+means either the Program or any derivative work under copyright law:+that is to say, a work containing the Program or a portion of it,+either verbatim or with modifications and/or translated into another+language.  (Hereinafter, translation is included without limitation in+the term "modification".)  Each licensee is addressed as "you".++Activities other than copying, distribution and modification are not+covered by this License; they are outside its scope.  The act of+running the Program is not restricted, and the output from the Program+is covered only if its contents constitute a work based on the+Program (independent of having been made by running the Program).+Whether that is true depends on what the Program does.++  1. You may copy and distribute verbatim copies of the Program's+source code as you receive it, in any medium, provided that you+conspicuously and appropriately publish on each copy an appropriate+copyright notice and disclaimer of warranty; keep intact all the+notices that refer to this License and to the absence of any warranty;+and give any other recipients of the Program a copy of this License+along with the Program.++You may charge a fee for the physical act of transferring a copy, and+you may at your option offer warranty protection in exchange for a fee.++  2. You may modify your copy or copies of the Program or any portion+of it, thus forming a work based on the Program, and copy and+distribute such modifications or work under the terms of Section 1+above, provided that you also meet all of these conditions:++    a) You must cause the modified files to carry prominent notices+    stating that you changed the files and the date of any change.++    b) You must cause any work that you distribute or publish, that in+    whole or in part contains or is derived from the Program or any+    part thereof, to be licensed as a whole at no charge to all third+    parties under the terms of this License.++    c) If the modified program normally reads commands interactively+    when run, you must cause it, when started running for such+    interactive use in the most ordinary way, to print or display an+    announcement including an appropriate copyright notice and a+    notice that there is no warranty (or else, saying that you provide+    a warranty) and that users may redistribute the program under+    these conditions, and telling the user how to view a copy of this+    License.  (Exception: if the Program itself is interactive but+    does not normally print such an announcement, your work based on+    the Program is not required to print an announcement.)++These requirements apply to the modified work as a whole.  If+identifiable sections of that work are not derived from the Program,+and can be reasonably considered independent and separate works in+themselves, then this License, and its terms, do not apply to those+sections when you distribute them as separate works.  But when you+distribute the same sections as part of a whole which is a work based+on the Program, the distribution of the whole must be on the terms of+this License, whose permissions for other licensees extend to the+entire whole, and thus to each and every part regardless of who wrote it.++Thus, it is not the intent of this section to claim rights or contest+your rights to work written entirely by you; rather, the intent is to+exercise the right to control the distribution of derivative or+collective works based on the Program.++In addition, mere aggregation of another work not based on the Program+with the Program (or with a work based on the Program) on a volume of+a storage or distribution medium does not bring the other work under+the scope of this License.++  3. You may copy and distribute the Program (or a work based on it,+under Section 2) in object code or executable form under the terms of+Sections 1 and 2 above provided that you also do one of the following:++    a) Accompany it with the complete corresponding machine-readable+    source code, which must be distributed under the terms of Sections+    1 and 2 above on a medium customarily used for software interchange; or,++    b) Accompany it with a written offer, valid for at least three+    years, to give any third party, for a charge no more than your+    cost of physically performing source distribution, a complete+    machine-readable copy of the corresponding source code, to be+    distributed under the terms of Sections 1 and 2 above on a medium+    customarily used for software interchange; or,++    c) Accompany it with the information you received as to the offer+    to distribute corresponding source code.  (This alternative is+    allowed only for noncommercial distribution and only if you+    received the program in object code or executable form with such+    an offer, in accord with Subsection b above.)++The source code for a work means the preferred form of the work for+making modifications to it.  For an executable work, complete source+code means all the source code for all modules it contains, plus any+associated interface definition files, plus the scripts used to+control compilation and installation of the executable.  However, as a+special exception, the source code distributed need not include+anything that is normally distributed (in either source or binary+form) with the major components (compiler, kernel, and so on) of the+operating system on which the executable runs, unless that component+itself accompanies the executable.++If distribution of executable or object code is made by offering+access to copy from a designated place, then offering equivalent+access to copy the source code from the same place counts as+distribution of the source code, even though third parties are not+compelled to copy the source along with the object code.++  4. You may not copy, modify, sublicense, or distribute the Program+except as expressly provided under this License.  Any attempt+otherwise to copy, modify, sublicense or distribute the Program is+void, and will automatically terminate your rights under this License.+However, parties who have received copies, or rights, from you under+this License will not have their licenses terminated so long as such+parties remain in full compliance.++  5. You are not required to accept this License, since you have not+signed it.  However, nothing else grants you permission to modify or+distribute the Program or its derivative works.  These actions are+prohibited by law if you do not accept this License.  Therefore, by+modifying or distributing the Program (or any work based on the+Program), you indicate your acceptance of this License to do so, and+all its terms and conditions for copying, distributing or modifying+the Program or works based on it.++  6. Each time you redistribute the Program (or any work based on the+Program), the recipient automatically receives a license from the+original licensor to copy, distribute or modify the Program subject to+these terms and conditions.  You may not impose any further+restrictions on the recipients' exercise of the rights granted herein.+You are not responsible for enforcing compliance by third parties to+this License.++  7. If, as a consequence of a court judgment or allegation of patent+infringement or for any other reason (not limited to patent issues),+conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License.  If you cannot+distribute so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you+may not distribute the Program at all.  For example, if a patent+license would not permit royalty-free redistribution of the Program by+all those who receive copies directly or indirectly through you, then+the only way you could satisfy both it and this License would be to+refrain entirely from distribution of the Program.++If any portion of this section is held invalid or unenforceable under+any particular circumstance, the balance of the section is intended to+apply and the section as a whole is intended to apply in other+circumstances.++It is not the purpose of this section to induce you to infringe any+patents or other property right claims or to contest validity of any+such claims; this section has the sole purpose of protecting the+integrity of the free software distribution system, which is+implemented by public license practices.  Many people have made+generous contributions to the wide range of software distributed+through that system in reliance on consistent application of that+system; it is up to the author/donor to decide if he or she is willing+to distribute software through any other system and a licensee cannot+impose that choice.++This section is intended to make thoroughly clear what is believed to+be a consequence of the rest of this License.++  8. If the distribution and/or use of the Program is restricted in+certain countries either by patents or by copyrighted interfaces, the+original copyright holder who places the Program under this License+may add an explicit geographical distribution limitation excluding+those countries, so that distribution is permitted only in or among+countries not thus excluded.  In such case, this License incorporates+the limitation as if written in the body of this License.++  9. The Free Software Foundation may publish revised and/or new versions+of the General Public License from time to time.  Such new versions will+be similar in spirit to the present version, but may differ in detail to+address new problems or concerns.++Each version is given a distinguishing version number.  If the Program+specifies a version number of this License which applies to it and "any+later version", you have the option of following the terms and conditions+either of that version or of any later version published by the Free+Software Foundation.  If the Program does not specify a version number of+this License, you may choose any version ever published by the Free Software+Foundation.++  10. If you wish to incorporate parts of the Program into other free+programs whose distribution conditions are different, write to the author+to ask for permission.  For software which is copyrighted by the Free+Software Foundation, write to the Free Software Foundation; we sometimes+make exceptions for this.  Our decision will be guided by the two goals+of preserving the free status of all derivatives of our free software and+of promoting the sharing and reuse of software generally.++			    NO WARRANTY++  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,+REPAIR OR CORRECTION.++  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE+POSSIBILITY OF SUCH DAMAGES.++		     END OF TERMS AND CONDITIONS++	    How to Apply These Terms to Your New Programs++  If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these terms.++  To do so, attach the following notices to the program.  It is safest+to attach them to the start of each source file to most effectively+convey the exclusion of warranty; and each file should have at least+the "copyright" line and a pointer to where the full notice is found.++    <one line to give the program's name and a brief idea of what it does.>+    Copyright (C) <year>  <name of author>++    This program is free software; you can redistribute it and/or modify+    it under the terms of the GNU General Public License as published by+    the Free Software Foundation; either version 2 of the License, or+    (at your option) any later version.++    This program is distributed in the hope that it will be useful,+    but WITHOUT ANY WARRANTY; without even the implied warranty of+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+    GNU General Public License for more details.++    You should have received a copy of the GNU General Public License+    along with this program; if not, write to the Free Software+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA+++Also add information on how to contact you by electronic and paper mail.++If the program is interactive, make it output a short notice like this+when it starts in an interactive mode:++    Gnomovision version 69, Copyright (C) year name of author+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.+    This is free software, and you are welcome to redistribute it+    under certain conditions; type `show c' for details.++The hypothetical commands `show w' and `show c' should show the appropriate+parts of the General Public License.  Of course, the commands you use may+be called something other than `show w' and `show c'; they could even be+mouse-clicks or menu items--whatever suits your program.++You should also get your employer (if you work as a programmer) or your+school, if any, to sign a "copyright disclaimer" for the program, if+necessary.  Here is a sample; alter the names:++  Yoyodyne, Inc., hereby disclaims all copyright interest in the program+  `Gnomovision' (which makes passes at compilers) written by James Hacker.++  <signature of Ty Coon>, 1 April 1989+  Ty Coon, President of Vice++This General Public License does not permit incorporating your program into+proprietary programs.  If your program is a subroutine library, you may+consider it more useful to permit linking proprietary applications with the+library.  If this is what you want to do, use the GNU Library General+Public License instead of this License.
+ cbits/linux/Linux-syscall-note view
@@ -0,0 +1,25 @@+SPDX-Exception-Identifier: Linux-syscall-note+SPDX-URL: https://spdx.org/licenses/Linux-syscall-note.html+SPDX-Licenses: GPL-2.0, GPL-2.0+, GPL-1.0+, LGPL-2.0, LGPL-2.0+, LGPL-2.1, LGPL-2.1+, GPL-2.0-only, GPL-2.0-or-later+Usage-Guide:+  This exception is used together with one of the above SPDX-Licenses+  to mark user space API (uapi) header files so they can be included+  into non GPL compliant user space application code.+  To use this exception add it with the keyword WITH to one of the+  identifiers in the SPDX-Licenses tag:+    SPDX-License-Identifier: <SPDX-License> WITH Linux-syscall-note+License-Text:++   NOTE! This copyright does *not* cover user programs that use kernel+ services by normal system calls - this is merely considered normal use+ of the kernel, and does *not* fall under the heading of "derived work".+ Also note that the GPL below is copyrighted by the Free Software+ Foundation, but the instance of code that it refers to (the Linux+ kernel) is copyrighted by me and others who actually wrote it.++ Also note that the only valid version of the GPL as far as the kernel+ is concerned is _this_ particular version of the license (ie v2, not+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.++			Linus Torvalds+
+ cbits/linux/landlock.h view
@@ -0,0 +1,176 @@+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */+/*+ * Landlock - User space API+ *+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>+ * Copyright © 2018-2020 ANSSI+ */++#ifndef _UAPI_LINUX_LANDLOCK_H+#define _UAPI_LINUX_LANDLOCK_H++#include <linux/types.h>++/**+ * struct landlock_ruleset_attr - Ruleset definition+ *+ * Argument of sys_landlock_create_ruleset().  This structure can grow in+ * future versions.+ */+struct landlock_ruleset_attr {+	/**+	 * @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_)+	 * that is handled by this ruleset and should then be forbidden if no+	 * rule explicitly allow them: it is a deny-by-default list that should+	 * contain as much Landlock access rights as possible. Indeed, all+	 * Landlock filesystem access rights that are not part of+	 * handled_access_fs are allowed.  This is needed for backward+	 * compatibility reasons.  One exception is the+	 * %LANDLOCK_ACCESS_FS_REFER access right, which is always implicitly+	 * handled, but must still be explicitly handled to add new rules with+	 * this access right.+	 */+	__u64 handled_access_fs;+};++/*+ * sys_landlock_create_ruleset() flags:+ *+ * - %LANDLOCK_CREATE_RULESET_VERSION: Get the highest supported Landlock ABI+ *   version.+ */+/* clang-format off */+#define LANDLOCK_CREATE_RULESET_VERSION			(1U << 0)+/* clang-format on */++/**+ * enum landlock_rule_type - Landlock rule type+ *+ * Argument of sys_landlock_add_rule().+ */+enum landlock_rule_type {+	/**+	 * @LANDLOCK_RULE_PATH_BENEATH: Type of a &struct+	 * landlock_path_beneath_attr .+	 */+	LANDLOCK_RULE_PATH_BENEATH = 1,+};++/**+ * struct landlock_path_beneath_attr - Path hierarchy definition+ *+ * Argument of sys_landlock_add_rule().+ */+struct landlock_path_beneath_attr {+	/**+	 * @allowed_access: Bitmask of allowed actions for this file hierarchy+	 * (cf. `Filesystem flags`_).+	 */+	__u64 allowed_access;+	/**+	 * @parent_fd: File descriptor, preferably opened with ``O_PATH``,+	 * which identifies the parent directory of a file hierarchy, or just a+	 * file.+	 */+	__s32 parent_fd;+	/*+	 * This struct is packed to avoid trailing reserved members.+	 * Cf. security/landlock/syscalls.c:build_check_abi()+	 */+} __attribute__((packed));++/**+ * DOC: fs_access+ *+ * A set of actions on kernel objects may be defined by an attribute (e.g.+ * &struct landlock_path_beneath_attr) including a bitmask of access.+ *+ * Filesystem flags+ * ~~~~~~~~~~~~~~~~+ *+ * These flags enable to restrict a sandboxed process to a set of actions on+ * files and directories.  Files or directories opened before the sandboxing+ * are not subject to these restrictions.+ *+ * A file can only receive these access rights:+ *+ * - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.+ * - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. Note that+ *   you might additionally need the %LANDLOCK_ACCESS_FS_TRUNCATE right in order+ *   to overwrite files with :manpage:`open(2)` using ``O_TRUNC`` or+ *   :manpage:`creat(2)`.+ * - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access.+ * - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file with :manpage:`truncate(2)`,+ *   :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with+ *   ``O_TRUNC``. Whether an opened file can be truncated with+ *   :manpage:`ftruncate(2)` is determined during :manpage:`open(2)`, in the+ *   same way as read and write permissions are checked during+ *   :manpage:`open(2)` using %LANDLOCK_ACCESS_FS_READ_FILE and+ *   %LANDLOCK_ACCESS_FS_WRITE_FILE. This access right is available since the+ *   third version of the Landlock ABI.+ *+ * A directory can receive access rights related to files or directories.  The+ * following access right is applied to the directory itself, and the+ * directories beneath it:+ *+ * - %LANDLOCK_ACCESS_FS_READ_DIR: Open a directory or list its content.+ *+ * However, the following access rights only apply to the content of a+ * directory, not the directory itself:+ *+ * - %LANDLOCK_ACCESS_FS_REMOVE_DIR: Remove an empty directory or rename one.+ * - %LANDLOCK_ACCESS_FS_REMOVE_FILE: Unlink (or rename) a file.+ * - %LANDLOCK_ACCESS_FS_MAKE_CHAR: Create (or rename or link) a character+ *   device.+ * - %LANDLOCK_ACCESS_FS_MAKE_DIR: Create (or rename) a directory.+ * - %LANDLOCK_ACCESS_FS_MAKE_REG: Create (or rename or link) a regular file.+ * - %LANDLOCK_ACCESS_FS_MAKE_SOCK: Create (or rename or link) a UNIX domain+ *   socket.+ * - %LANDLOCK_ACCESS_FS_MAKE_FIFO: Create (or rename or link) a named pipe.+ * - %LANDLOCK_ACCESS_FS_MAKE_BLOCK: Create (or rename or link) a block device.+ * - %LANDLOCK_ACCESS_FS_MAKE_SYM: Create (or rename or link) a symbolic link.+ * - %LANDLOCK_ACCESS_FS_REFER: Link or rename a file from or to a different+ *   directory (i.e. reparent a file hierarchy).  This access right is+ *   available since the second version of the Landlock ABI.  This is also the+ *   only access right which is always considered handled by any ruleset in+ *   such a way that reparenting a file hierarchy is always denied by default.+ *   To avoid privilege escalation, it is not enough to add a rule with this+ *   access right.  When linking or renaming a file, the destination directory+ *   hierarchy must also always have the same or a superset of restrictions of+ *   the source hierarchy.  If it is not the case, or if the domain doesn't+ *   handle this access right, such actions are denied by default with errno+ *   set to ``EXDEV``.  Linking also requires a ``LANDLOCK_ACCESS_FS_MAKE_*``+ *   access right on the destination directory, and renaming also requires a+ *   ``LANDLOCK_ACCESS_FS_REMOVE_*`` access right on the source's (file or+ *   directory) parent.  Otherwise, such actions are denied with errno set to+ *   ``EACCES``.  The ``EACCES`` errno prevails over ``EXDEV`` to let user space+ *   efficiently deal with an unrecoverable error.+ *+ * .. warning::+ *+ *   It is currently not possible to restrict some file-related actions+ *   accessible through these syscall families: :manpage:`chdir(2)`,+ *   :manpage:`stat(2)`, :manpage:`flock(2)`, :manpage:`chmod(2)`,+ *   :manpage:`chown(2)`, :manpage:`setxattr(2)`, :manpage:`utime(2)`,+ *   :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, :manpage:`access(2)`.+ *   Future Landlock evolutions will enable to restrict them.+ */+/* clang-format off */+#define LANDLOCK_ACCESS_FS_EXECUTE			(1ULL << 0)+#define LANDLOCK_ACCESS_FS_WRITE_FILE			(1ULL << 1)+#define LANDLOCK_ACCESS_FS_READ_FILE			(1ULL << 2)+#define LANDLOCK_ACCESS_FS_READ_DIR			(1ULL << 3)+#define LANDLOCK_ACCESS_FS_REMOVE_DIR			(1ULL << 4)+#define LANDLOCK_ACCESS_FS_REMOVE_FILE			(1ULL << 5)+#define LANDLOCK_ACCESS_FS_MAKE_CHAR			(1ULL << 6)+#define LANDLOCK_ACCESS_FS_MAKE_DIR			(1ULL << 7)+#define LANDLOCK_ACCESS_FS_MAKE_REG			(1ULL << 8)+#define LANDLOCK_ACCESS_FS_MAKE_SOCK			(1ULL << 9)+#define LANDLOCK_ACCESS_FS_MAKE_FIFO			(1ULL << 10)+#define LANDLOCK_ACCESS_FS_MAKE_BLOCK			(1ULL << 11)+#define LANDLOCK_ACCESS_FS_MAKE_SYM			(1ULL << 12)+#define LANDLOCK_ACCESS_FS_REFER			(1ULL << 13)+#define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)+/* clang-format on */++#endif /* _UAPI_LINUX_LANDLOCK_H */
+ internal/System/Landlock/Flags.hs view
@@ -0,0 +1,247 @@+{-# LANGUAGE EmptyCase #-}+{-# LANGUAGE EmptyDataDeriving #-}+{-# LANGUAGE LambdaCase #-}++module System.Landlock.Flags+  ( toBits,+    fromBits,+    AccessFsFlag (..),+    accessFsFlagToBit,+    accessFsFlags,+    accessFsFlagIsReadOnly,+    CreateRulesetFlag (..),+    createRulesetFlagToBit,+    RestrictSelfFlag,+    restrictSelfFlagToBit,+    AddRuleFlag,+    addRuleFlagToBit,+  )+where++import Data.Bits (Bits, (.&.), (.|.))+import System.Landlock.Hsc+  ( U32,+    U64,+    lANDLOCK_ACCESS_FS_EXECUTE,+    lANDLOCK_ACCESS_FS_MAKE_BLOCK,+    lANDLOCK_ACCESS_FS_MAKE_CHAR,+    lANDLOCK_ACCESS_FS_MAKE_DIR,+    lANDLOCK_ACCESS_FS_MAKE_FIFO,+    lANDLOCK_ACCESS_FS_MAKE_REG,+    lANDLOCK_ACCESS_FS_MAKE_SOCK,+    lANDLOCK_ACCESS_FS_MAKE_SYM,+    lANDLOCK_ACCESS_FS_READ_DIR,+    lANDLOCK_ACCESS_FS_READ_FILE,+    lANDLOCK_ACCESS_FS_REFER,+    lANDLOCK_ACCESS_FS_REMOVE_DIR,+    lANDLOCK_ACCESS_FS_REMOVE_FILE,+    lANDLOCK_ACCESS_FS_TRUNCATE,+    lANDLOCK_ACCESS_FS_WRITE_FILE,+    lANDLOCK_CREATE_RULESET_VERSION,+  )+import System.Landlock.Version (Version, version1, version2, version3)++-- | Fold a set of flags into a bitset/number.+toBits :: (Num b, Bits b, Foldable f) => (a -> b) -> f a -> b+toBits f = foldr (\a b -> b .|. f a) 0++-- | Expand a bitset/number into a set of flags.+fromBits :: (Enum a, Bounded a, Num b, Bits b) => (a -> b) -> b -> [a]+fromBits f b = filter (\a -> b .&. f a /= 0) [minBound .. maxBound]++-- | Filesystem flags.+--+-- These flags enable to restrict a sandboxed process to a set of actions on+-- files and directories. Files or directories opened before the sandboxing+-- are not subject to these restrictions.+--+-- A file can only receive these access rights:+--+-- - 'AccessFsExecute'+-- - 'AccessFsWriteFile'+-- - 'AccessFsReadFile'+-- - 'AccessFsTruncate'+--+-- A directory can receive access rights related to files or directories. The+-- following access right is applied to the directory itself, and the+-- directories beneath it:+--+-- - 'AccessFsReadDir'+--+-- However, the following access rights only apply to the content of a+-- directory, not the directory itself:+--+-- - 'AccessFsRemoveDir'+-- - 'AccessFsRemoveFile'+-- - 'AccessFsMakeChar'+-- - 'AccessFsMakeDir'+-- - 'AccessFsMakeReg'+-- - 'AccessFsMakeSock'+-- - 'AccessFsMakeFifo'+-- - 'AccessFsMakeBlock'+-- - 'AccessFsMakeSym'+-- - 'AccessFsRefer'+--+-- __Warning:__ It is currently not possible to restrict some file-related+-- actions acessible through these syscall families: @chdir@,+-- @stat@, @flock@, @chmod@, @chown@, @setxattr@, @utime@, @ioctl@, @fcntl@,+-- @access@. Future Landlock evolutions will enable to restrict them.+data AccessFsFlag+  = -- | Execute a file.+    AccessFsExecute+  | -- | Open a file with write access.+    AccessFsWriteFile+  | -- | Open a file with read access. Note that+    --   you might additionally need the @LANDLOCK_ACCESS_FS_TRUNCATE@ right in order+    --   to overwrite files with @open@ using @O_TRUNC@ or+    --   @creat@.+    AccessFsReadFile+  | -- | Open a directory or list its content.+    AccessFsReadDir+  | -- | Remove an empty directory or rename one+    AccessFsRemoveDir+  | -- | Unlink (or rename) a file.+    AccessFsRemoveFile+  | -- | Create (or rename or link) a character device.+    AccessFsMakeChar+  | -- | Create (or rename) a directory.+    AccessFsMakeDir+  | -- | Create (or rename or link) a regular file.+    AccessFsMakeReg+  | -- | Create (or rename or link) a UNIX domain socket.+    AccessFsMakeSock+  | -- | Create (or rename or link) a named pipe.+    AccessFsMakeFifo+  | -- | Create (or rename or link) a block device.+    AccessFsMakeBlock+  | -- | Create (or rename or link) a symbolic link.+    AccessFsMakeSym+  | -- | Link or rename a file from or to a different+    --   directory (i.e. reparent a file hierarchy).  This access right is+    --   available since the second version of the Landlock ABI.  This is also the+    --   only access right which is always considered handled by any ruleset in+    --   such a way that reparenting a file hierarchy is always denied by default.+    --   To avoid privilege escalation, it is not enough to add a rule with this+    --   access right.  When linking or renaming a file, the destination directory+    --   hierarchy must also always have the same or a superset of restrictions of+    --   the source hierarchy.  If it is not the case, or if the domain doesn't+    --   handle this access right, such actions are denied by default with @errno@+    --   set to @EXDEV@.  Linking also requires a @LANDLOCK_ACCESS_FS_MAKE_*@ access+    --   right on the destination directory, and renaming also requires a+    --   @LANDLOCK_ACCESS_FS_REMOVE_*@ access right on the source's (file or+    --   directory) parent.  Otherwise, such actions are denied with @errno@ set to+    --   @EACCES@.  The @EACCES@ @errno@ prevails over @EXDEV@ to let user space+    --   efficiently deal with an unrecoverable error.+    AccessFsRefer+  | -- | Truncate a file with @truncate@,+    --   @ftruncate@, @creat@, or @open@ with+    --   @O_TRUNC@. Whether an opened file can be truncated with+    --   @ftruncate@ is determined during @open@, in the+    --   same way as read and write permissions are checked during+    --   @open@ using @LANDLOCK_ACCESS_FS_READ_FILE@ and+    --   @LANDLOCK_ACCESS_FS_WRITE_FILE@. This access right is available since the+    --   third version of the Landlock ABI.+    AccessFsTruncate+  -- Note: when adding new flags, this likely means a new ABI version is+  -- available. Hence, add this to 'System.Landlock.Version' (and export it+  -- from 'System.Landlock'), and make sure to update both 'accessFsFlags' and+  -- 'accessFsFlagIsReadOnly'.+  deriving (Show, Eq, Enum, Bounded, Ord)++-- | Retrieve a number with the appropriate 'AccessFsFlag' bit set.+accessFsFlagToBit :: AccessFsFlag -> U64+accessFsFlagToBit = \case+  AccessFsExecute -> lANDLOCK_ACCESS_FS_EXECUTE+  AccessFsWriteFile -> lANDLOCK_ACCESS_FS_WRITE_FILE+  AccessFsReadFile -> lANDLOCK_ACCESS_FS_READ_FILE+  AccessFsReadDir -> lANDLOCK_ACCESS_FS_READ_DIR+  AccessFsRemoveDir -> lANDLOCK_ACCESS_FS_REMOVE_DIR+  AccessFsRemoveFile -> lANDLOCK_ACCESS_FS_REMOVE_FILE+  AccessFsMakeChar -> lANDLOCK_ACCESS_FS_MAKE_CHAR+  AccessFsMakeDir -> lANDLOCK_ACCESS_FS_MAKE_DIR+  AccessFsMakeReg -> lANDLOCK_ACCESS_FS_MAKE_REG+  AccessFsMakeSock -> lANDLOCK_ACCESS_FS_MAKE_SOCK+  AccessFsMakeFifo -> lANDLOCK_ACCESS_FS_MAKE_FIFO+  AccessFsMakeBlock -> lANDLOCK_ACCESS_FS_MAKE_BLOCK+  AccessFsMakeSym -> lANDLOCK_ACCESS_FS_MAKE_SYM+  AccessFsRefer -> lANDLOCK_ACCESS_FS_REFER+  AccessFsTruncate -> lANDLOCK_ACCESS_FS_TRUNCATE++-- | All 'AccessFsFlag' flags keyed by a Landlock ABI 'Version'.+accessFsFlags :: [(Version, [AccessFsFlag])]+accessFsFlags =+  [ (v, [f | f <- [minBound ..], version f <= v])+    | v <-+        [ version1,+          version2,+          version3+        ]+  ]+  where+    version = \case+      AccessFsExecute -> version1+      AccessFsWriteFile -> version1+      AccessFsReadFile -> version1+      AccessFsReadDir -> version1+      AccessFsRemoveDir -> version1+      AccessFsRemoveFile -> version1+      AccessFsMakeChar -> version1+      AccessFsMakeDir -> version1+      AccessFsMakeReg -> version1+      AccessFsMakeSock -> version1+      AccessFsMakeFifo -> version1+      AccessFsMakeBlock -> version1+      AccessFsMakeSym -> version1+      AccessFsRefer -> version2+      AccessFsTruncate -> version3++-- | Predicate for read-only 'AccessFsFlag' flags.+accessFsFlagIsReadOnly :: AccessFsFlag -> Bool+accessFsFlagIsReadOnly = \case+  AccessFsExecute -> True+  AccessFsWriteFile -> False+  AccessFsReadFile -> True+  AccessFsReadDir -> True+  AccessFsRemoveDir -> False+  AccessFsRemoveFile -> False+  AccessFsMakeChar -> False+  AccessFsMakeDir -> False+  AccessFsMakeReg -> False+  AccessFsMakeSock -> False+  AccessFsMakeFifo -> False+  AccessFsMakeBlock -> False+  AccessFsMakeSym -> False+  AccessFsRefer -> False+  AccessFsTruncate -> False++-- | Flags passed to @landlock_create_ruleset@.+--+-- In the current kernel API, only @LANDLOCK_CREATE_RULESET_VERSION@ is+-- defined, which should not be used when creating an actual Landlock+-- encironment (cf. 'landlock').+data CreateRulesetFlag = CreateRulesetVersion+  deriving (Show, Eq, Enum, Bounded)++createRulesetFlagToBit :: CreateRulesetFlag -> U32+createRulesetFlagToBit = \case+  CreateRulesetVersion -> lANDLOCK_CREATE_RULESET_VERSION++-- | Flags passed to @landlock_restrict_self@.+--+-- In the current kernel API, no such flags are defined, hence this is a type+-- without any constructors.+data RestrictSelfFlag+  deriving (Show, Eq)++restrictSelfFlagToBit :: RestrictSelfFlag -> U32+restrictSelfFlagToBit = \case {}++-- | Flags passed to @landlock_add_rule@.+--+-- In the current kernel API, no such flags are defined, hence this is a type+-- without any constructors.+data AddRuleFlag+  deriving (Show, Eq)++addRuleFlagToBit :: AddRuleFlag -> U32+addRuleFlagToBit = \case {}
− internal/System/Landlock/Flags.hsc
@@ -1,119 +0,0 @@-{-# LANGUAGE LambdaCase #-}--module System.Landlock.Flags (-      toBits-    , fromBits-    , AccessFsFlag(..)-    , accessFsFlagToBit-    , accessFsFlags-    , accessFsFlagIsReadOnly-    ) where--#include <linux/landlock.h>--import Data.Bits (Bits, (.|.), (.&.))--import System.Landlock.Version (Version, version1)---- | Fold a set of flags into a bitset/number.-toBits :: (Num b, Bits b, Foldable f) => f a -> (a -> b) -> b-toBits l f = foldr (\a b -> b .|. f a) 0 l---- | Expand a bitset/number into a set of flags.-fromBits :: (Enum a, Bounded a, Num b, Bits b) => b -> (a -> b) -> [a]-fromBits b f = filter (\a -> b .&. f a /= 0) [minBound .. maxBound]---- | Filesystem flags.------ These flags enable to restrict a sandboxed process to a set of actions on--- files and directories. Files or directories opened before the sandboxing--- are not subject to these restrictions.------ A file can only receive these access rights:------ - 'AccessFsExecute'--- - 'AccessFsWriteFile'--- - 'AccessFsReadFile'------ A directory can receive access rights related to files or directories. The--- following access right is applied to the directory itself, and the--- directories beneath it:------ - 'AccessFsReadDir'------ However, the following access rights only apply to the content of a--- directory, not the directory itself:------ - 'AccessFsRemoveDir'--- - 'AccessFsRemoveFile'--- - 'AccessFsMakeChar'--- - 'AccessFsMakeDir'--- - 'AccessFsMakeReg'--- - 'AccessFsMakeSock'--- - 'AccessFsMakeFifo'--- - 'AccessFsMakeBlock'--- - 'AccessFsMakeSym'------ __Warning:__ It is currently not possible to restrict some file-related--- actions acessible through these syscall families: @chdir@, @truncate@,--- @stat@, @flock@, @chmod@, @chown@, @setxattr@, @utime@, @ioctl@, @fcntl@,--- @access@. Future Landlock evolutions will enable to restrict them.-data AccessFsFlag = AccessFsExecute     -- ^ Execute a file.-                  | AccessFsWriteFile   -- ^ Open a file with write access.-                  | AccessFsReadFile    -- ^ Open a file with read access.-                  | AccessFsReadDir     -- ^ Open a directory or list its content.-                  | AccessFsRemoveDir   -- ^ Remove an empty directory or rename one-                  | AccessFsRemoveFile  -- ^ Unlink (or rename) a file.-                  | AccessFsMakeChar    -- ^ Create (or rename or link) a character device.-                  | AccessFsMakeDir     -- ^ Create (or rename) a directory.-                  | AccessFsMakeReg     -- ^ Create (or rename or link) a regular file.-                  | AccessFsMakeSock    -- ^ Create (or rename or link) a UNIX domain socket.-                  | AccessFsMakeFifo    -- ^ Create (or rename or link) a named pipe.-                  | AccessFsMakeBlock   -- ^ Create (or rename or link) a block device.-                  | AccessFsMakeSym     -- ^ Create (or rename or link) a symbolic link.---- Note: when adding new flags, this likely means a new ABI version is--- available. Hence, add this to 'System.Landlock.Version' (and export it--- from 'System.Landlock'), and make sure to update both 'accessFsFlags' and--- 'accessFsFlagIsReadOnly'.-  deriving (Show, Eq, Enum, Bounded, Ord)---- | Retrieve a number with the appropriate 'AccessFsFlag' bit set.-accessFsFlagToBit :: Num a => AccessFsFlag -> a-accessFsFlagToBit = \case-    AccessFsExecute -> #{const LANDLOCK_ACCESS_FS_EXECUTE}-    AccessFsWriteFile -> #{const LANDLOCK_ACCESS_FS_WRITE_FILE}-    AccessFsReadFile -> #{const LANDLOCK_ACCESS_FS_READ_FILE}-    AccessFsReadDir -> #{const LANDLOCK_ACCESS_FS_READ_DIR}-    AccessFsRemoveDir -> #{const LANDLOCK_ACCESS_FS_REMOVE_DIR}-    AccessFsRemoveFile -> #{const LANDLOCK_ACCESS_FS_REMOVE_FILE}-    AccessFsMakeChar -> #{const LANDLOCK_ACCESS_FS_MAKE_CHAR}-    AccessFsMakeDir -> #{const LANDLOCK_ACCESS_FS_MAKE_DIR}-    AccessFsMakeReg -> #{const LANDLOCK_ACCESS_FS_MAKE_REG}-    AccessFsMakeSock -> #{const LANDLOCK_ACCESS_FS_MAKE_SOCK}-    AccessFsMakeFifo -> #{const LANDLOCK_ACCESS_FS_MAKE_FIFO}-    AccessFsMakeBlock -> #{const LANDLOCK_ACCESS_FS_MAKE_BLOCK}-    AccessFsMakeSym -> #{const LANDLOCK_ACCESS_FS_MAKE_SYM}---- | All 'AccessFsFlag' flags keyed by a Landlock ABI 'Version'.-accessFsFlags :: [(Version, [AccessFsFlag])]-accessFsFlags = [-      (version1, [AccessFsExecute .. AccessFsMakeSym])-    ]---- | Predicate for read-only 'AccessFsFlag' flags.-accessFsFlagIsReadOnly :: AccessFsFlag -> Bool-accessFsFlagIsReadOnly = \case-    AccessFsExecute -> True-    AccessFsWriteFile -> False-    AccessFsReadFile -> True-    AccessFsReadDir -> True-    AccessFsRemoveDir -> False-    AccessFsRemoveFile -> False-    AccessFsMakeChar -> False-    AccessFsMakeDir -> False-    AccessFsMakeReg -> False-    AccessFsMakeSock -> False-    AccessFsMakeFifo -> False-    AccessFsMakeBlock -> False-    AccessFsMakeSym -> False
+ internal/System/Landlock/Hsc.hsc view
@@ -0,0 +1,179 @@+module System.Landlock.Hsc (+  aT_FDCWD,++  o_PATH,+  o_RDONLY,+  o_DIRECTORY,+  o_NOFOLLOW,+  o_CLOEXEC,++  lANDLOCK_ACCESS_FS_EXECUTE,+  lANDLOCK_ACCESS_FS_WRITE_FILE,+  lANDLOCK_ACCESS_FS_READ_FILE,+  lANDLOCK_ACCESS_FS_READ_DIR,+  lANDLOCK_ACCESS_FS_REMOVE_DIR,+  lANDLOCK_ACCESS_FS_REMOVE_FILE,+  lANDLOCK_ACCESS_FS_MAKE_CHAR,+  lANDLOCK_ACCESS_FS_MAKE_DIR,+  lANDLOCK_ACCESS_FS_MAKE_REG,+  lANDLOCK_ACCESS_FS_MAKE_SOCK,+  lANDLOCK_ACCESS_FS_MAKE_FIFO,+  lANDLOCK_ACCESS_FS_MAKE_BLOCK,+  lANDLOCK_ACCESS_FS_MAKE_SYM,+  lANDLOCK_ACCESS_FS_REFER,+  lANDLOCK_ACCESS_FS_TRUNCATE,++  lANDLOCK_CREATE_RULESET_VERSION,++  U32,+  U64,+  Landlock_rule_type,++  pR_SET_NO_NEW_PRIVS,++  landlock_ruleset_attr_size,+  landlock_ruleset_attr_alignment,+  landlock_ruleset_attr_peek_handled_access_fs,+  landlock_ruleset_attr_poke_handled_access_fs,++  lANDLOCK_RULE_PATH_BENEATH,+  landlock_path_beneath_attr_size,+  landlock_path_beneath_attr_alignment,+  landlock_path_beneath_attr_peek_allowed_access,+  landlock_path_beneath_attr_poke_allowed_access,+  landlock_path_beneath_attr_peek_parent_fd,+  landlock_path_beneath_attr_poke_parent_fd,+) where++#define _GNU_SOURCE++#include <fcntl.h>+#include <stddef.h>+#include <sys/prctl.h>+#include <sys/types.h>++#include "hs-landlock.h"+#include "linux/landlock.h"++import Data.Int (Int32)+import Data.Word (Word32, Word64)+import Foreign.C.Types (CInt)+import Foreign.Ptr (Ptr)+import Foreign.Storable (peekByteOff, pokeByteOff)++aT_FDCWD :: CInt+aT_FDCWD = #{const AT_FDCWD}++o_PATH :: CInt+o_PATH = #{const O_PATH}++o_RDONLY :: CInt+o_RDONLY = #{const O_RDONLY}++o_DIRECTORY :: CInt+o_DIRECTORY = #{const O_DIRECTORY}++o_NOFOLLOW :: CInt+o_NOFOLLOW = #{const O_NOFOLLOW}++o_CLOEXEC :: CInt+o_CLOEXEC = #{const O_CLOEXEC}++lANDLOCK_ACCESS_FS_EXECUTE :: #{type __u64}+lANDLOCK_ACCESS_FS_EXECUTE = #{const LANDLOCK_ACCESS_FS_EXECUTE}++lANDLOCK_ACCESS_FS_WRITE_FILE :: #{type __u64}+lANDLOCK_ACCESS_FS_WRITE_FILE = #{const LANDLOCK_ACCESS_FS_WRITE_FILE}++lANDLOCK_ACCESS_FS_READ_FILE :: #{type __u64}+lANDLOCK_ACCESS_FS_READ_FILE = #{const LANDLOCK_ACCESS_FS_READ_FILE}++lANDLOCK_ACCESS_FS_READ_DIR :: #{type __u64}+lANDLOCK_ACCESS_FS_READ_DIR = #{const LANDLOCK_ACCESS_FS_READ_DIR}++lANDLOCK_ACCESS_FS_REMOVE_DIR :: #{type __u64}+lANDLOCK_ACCESS_FS_REMOVE_DIR = #{const LANDLOCK_ACCESS_FS_REMOVE_DIR}++lANDLOCK_ACCESS_FS_REMOVE_FILE :: #{type __u64}+lANDLOCK_ACCESS_FS_REMOVE_FILE = #{const LANDLOCK_ACCESS_FS_REMOVE_FILE}++lANDLOCK_ACCESS_FS_MAKE_CHAR :: #{type __u64}+lANDLOCK_ACCESS_FS_MAKE_CHAR = #{const LANDLOCK_ACCESS_FS_MAKE_CHAR}++lANDLOCK_ACCESS_FS_MAKE_DIR :: #{type __u64}+lANDLOCK_ACCESS_FS_MAKE_DIR = #{const LANDLOCK_ACCESS_FS_MAKE_DIR}++lANDLOCK_ACCESS_FS_MAKE_REG :: #{type __u64}+lANDLOCK_ACCESS_FS_MAKE_REG = #{const LANDLOCK_ACCESS_FS_MAKE_REG}++lANDLOCK_ACCESS_FS_MAKE_SOCK :: #{type __u64}+lANDLOCK_ACCESS_FS_MAKE_SOCK = #{const LANDLOCK_ACCESS_FS_MAKE_SOCK}++lANDLOCK_ACCESS_FS_MAKE_FIFO :: #{type __u64}+lANDLOCK_ACCESS_FS_MAKE_FIFO = #{const LANDLOCK_ACCESS_FS_MAKE_FIFO}++lANDLOCK_ACCESS_FS_MAKE_BLOCK :: #{type __u64}+lANDLOCK_ACCESS_FS_MAKE_BLOCK = #{const LANDLOCK_ACCESS_FS_MAKE_BLOCK}++lANDLOCK_ACCESS_FS_MAKE_SYM :: #{type __u64}+lANDLOCK_ACCESS_FS_MAKE_SYM = #{const LANDLOCK_ACCESS_FS_MAKE_SYM}++lANDLOCK_ACCESS_FS_REFER :: #{type __u64}+lANDLOCK_ACCESS_FS_REFER = #{const LANDLOCK_ACCESS_FS_REFER}++lANDLOCK_ACCESS_FS_TRUNCATE :: #{type __u64}+lANDLOCK_ACCESS_FS_TRUNCATE = #{const LANDLOCK_ACCESS_FS_TRUNCATE}++lANDLOCK_CREATE_RULESET_VERSION :: #{type __u32}+lANDLOCK_CREATE_RULESET_VERSION = #{const LANDLOCK_CREATE_RULESET_VERSION}++type U32 = #{type __u32}+type U64 = #{type __u64}++type Landlock_rule_type = #{type enum landlock_rule_type}++pR_SET_NO_NEW_PRIVS :: CInt+pR_SET_NO_NEW_PRIVS = #{const PR_SET_NO_NEW_PRIVS}++landlock_ruleset_attr_size :: Int+landlock_ruleset_attr_size = #{size struct landlock_ruleset_attr}++landlock_ruleset_attr_alignment :: Int+landlock_ruleset_attr_alignment = #{alignment struct landlock_ruleset_attr}++landlock_ruleset_attr_peek_handled_access_fs :: Ptr a -> IO #{type __u64}+landlock_ruleset_attr_peek_handled_access_fs =+  #{peek struct landlock_ruleset_attr, handled_access_fs}++landlock_ruleset_attr_poke_handled_access_fs :: Ptr a -> #{type __u64} -> IO ()+landlock_ruleset_attr_poke_handled_access_fs =+  #{poke struct landlock_ruleset_attr, handled_access_fs}++lANDLOCK_RULE_PATH_BENEATH :: #{type enum landlock_rule_type}+lANDLOCK_RULE_PATH_BENEATH = #{const LANDLOCK_RULE_PATH_BENEATH}++landlock_path_beneath_attr_size :: Int+landlock_path_beneath_attr_size = #{size struct landlock_path_beneath_attr}++landlock_path_beneath_attr_alignment :: Int+landlock_path_beneath_attr_alignment =+  #{alignment struct landlock_path_beneath_attr}++landlock_path_beneath_attr_peek_allowed_access :: Ptr a -> IO #{type __u64}+landlock_path_beneath_attr_peek_allowed_access =+  #{peek struct landlock_path_beneath_attr, allowed_access}++landlock_path_beneath_attr_peek_parent_fd :: Ptr a -> IO #{type __s32}+landlock_path_beneath_attr_peek_parent_fd =+  #{peek struct landlock_path_beneath_attr, parent_fd}++landlock_path_beneath_attr_poke_allowed_access ::+  Ptr a -> #{type __u64} -> IO ()+landlock_path_beneath_attr_poke_allowed_access =+  #{poke struct landlock_path_beneath_attr, allowed_access}++landlock_path_beneath_attr_poke_parent_fd :: Ptr a -> #{type __s32} -> IO ()+landlock_path_beneath_attr_poke_parent_fd =+  #{poke struct landlock_path_beneath_attr, parent_fd}++
+ internal/System/Landlock/OpenPath.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE CApiFFI #-}++module System.Landlock.OpenPath+  ( withOpenPath,+    withOpenPathAt,+    OpenPathFlags (..),+    defaultOpenPathFlags,+  )+where++import Control.Monad.Catch (MonadMask, bracket)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Bits ((.|.))+import Foreign.C.String (CString, withCString)+import Foreign.C.Types (CInt (..))+import System.Landlock.Hsc+  ( aT_FDCWD,+    o_CLOEXEC,+    o_DIRECTORY,+    o_NOFOLLOW,+    o_PATH,+    o_RDONLY,+  )+import System.Posix.Error (throwErrnoPathIfMinus1Retry)+import System.Posix.IO (closeFd)+import System.Posix.Types (Fd)++foreign import capi unsafe "fcntl.h openat"+  _openat ::+    CInt ->+    CString ->+    CInt ->+    IO CInt++openat ::+  Fd ->+  FilePath ->+  CInt ->+  IO Fd+openat dirfd pathname flags = withCString pathname $ \pathnamep -> do+  throwErrnoPathIfMinus1Retry "openat" pathname $+    fromIntegral <$> _openat (fromIntegral dirfd) pathnamep flags++-- | Extra flags used by 'withOpenPathAt' in the call to @openat@.+data OpenPathFlags = OpenPathFlags+  { -- | Set @O_DIRECTORY@.+    directory :: Bool,+    -- | Set @O_NOFOLLOW@.+    nofollow :: Bool,+    -- | Set @O_CLOEXEC@.+    cloexec :: Bool+  }+  deriving (Show, Eq)++-- | Default 'OpenPathFlags':+--+-- - 'directory' is @False@.+-- - 'nofollow' is @False@.+-- - 'cloexec' is @True@.+defaultOpenPathFlags :: OpenPathFlags+defaultOpenPathFlags =+  OpenPathFlags+    { directory = False,+      nofollow = False,+      cloexec = True+    }++-- | Perform an action with a path @open@ed using @O_PATH@.+--+-- The file descriptor provided to the action will be @close@d when the+-- function returns.+--+-- This internally calls @openat@ with @AT_FDCWD@ and the @O_PATH@ and+-- @O_RDONLY@ flags set, next to any flags specified in the 'OpenPathFlags'+-- argument.+withOpenPath ::+  (MonadIO m, MonadMask m) =>+  -- | Path to open.+  FilePath ->+  -- | Flag settings to pass.+  OpenPathFlags ->+  -- | Action to call with a file descriptor to the given path.+  (Fd -> m a) ->+  -- | Result of the invoked action.+  m a+withOpenPath = withOpenPathAt (fromIntegral aT_FDCWD)++-- | Perform an action with a path @openat@ed using @O_PATH@.+--+-- Like 'withOpenPath', exposing the @openat@ @dirfd@ argument.+--+-- The file descriptor provided to the action will be @close@d when the+-- function returns.+--+-- This internally calls @openat@ with the @O_PATH@ and @O_RDONLY@ flags set,+-- next to any flags specified in the 'OpenPathFlags' argument.+withOpenPathAt ::+  (MonadIO m, MonadMask m) =>+  -- | @dirfd@ argument to @openat@.+  Fd ->+  -- | Path to open.+  FilePath ->+  -- | Flag settings to pass.+  OpenPathFlags ->+  -- | Action to call with a file descriptor to the given path.+  (Fd -> m a) ->+  -- | Result of the invoked action.+  m a+withOpenPathAt dirfd pathname flags =+  bracket+    (liftIO $ openat dirfd pathname flags')+    (liftIO . closeFd)+  where+    flags' =+      foldr+        (.|.)+        0+        [ o_PATH,+          o_RDONLY,+          if directory flags then o_DIRECTORY else 0,+          if nofollow flags then o_NOFOLLOW else 0,+          if cloexec flags then o_CLOEXEC else 0+        ]
− internal/System/Landlock/OpenPath.hsc
@@ -1,93 +0,0 @@-module System.Landlock.OpenPath (-      withOpenPath-    , withOpenPathAt-    , OpenPathFlags(..)-    , defaultOpenPathFlags-    ) where--#define _GNU_SOURCE--#include <fcntl.h>--import Control.Monad.Catch (MonadMask, bracket)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Bits ((.|.))-import Data.Int (Int32)-import Foreign.C.String (CString, withCString)-import System.Posix.Error (throwErrnoPathIfMinus1Retry)-import System.Posix.IO (closeFd)-import System.Posix.Types (Fd)--foreign import ccall unsafe "fcntl.h openat"-  _openat :: #{type int}-         -> CString-         -> #{type int}-         -> IO #{type int}--openat :: Fd-       -> FilePath-       -> #{type int}-       -> IO Fd-openat dirfd pathname flags = withCString pathname $ \pathnamep -> do-    fd <- throwErrnoPathIfMinus1Retry "openat" pathname $ _openat (fromIntegral dirfd) pathnamep flags-    return $ fromIntegral fd---- | Extra flags used by 'withOpenPathAt' in the call to @openat@.-data OpenPathFlags = OpenPathFlags { directory :: Bool-                                     -- ^ Set @O_DIRECTORY@.-                                   , nofollow :: Bool-                                     -- ^ Set @O_NOFOLLOW@.-                                   , cloexec :: Bool-                                     -- ^ Set @O_CLOEXEC@.-                                   }-  deriving (Show, Eq)---- | Default 'OpenPathFlags':------ - 'directory' is @False@.--- - 'nofollow' is @False@.--- - 'cloexec' is @True@.-defaultOpenPathFlags :: OpenPathFlags-defaultOpenPathFlags = OpenPathFlags { directory = False-                                     , nofollow = False-                                     , cloexec = True-                                     }---- | Perform an action with a path @open@ed using @O_PATH@.------ The file descriptor provided to the action will be @close@d when the--- function returns.------ This internally calls @openat@ with @AT_FDCWD@ and the @O_PATH@ and--- @O_RDONLY@ flags set, next to any flags specified in the 'OpenPathFlags'--- argument.-withOpenPath :: (MonadIO m, MonadMask m)-             => FilePath  -- ^ Path to open.-             -> OpenPathFlags  -- ^ Flag settings to pass.-             -> (Fd -> m a)  -- ^ Action to call with a file descriptor to the given path.-             -> m a  -- ^ Result of the invoked action.-withOpenPath = withOpenPathAt (fromIntegral (#{const AT_FDCWD} :: #{type int}))---- | Perform an action with a path @openat@ed using @O_PATH@.------ Like 'withOpenPath', exposing the @openat@ @dirfd@ argument.------ The file descriptor provided to the action will be @close@d when the--- function returns.------ This internally calls @openat@ with the @O_PATH@ and @O_RDONLY@ flags set,--- next to any flags specified in the 'OpenPathFlags' argument.-withOpenPathAt :: (MonadIO m, MonadMask m)-               => Fd  -- ^ @dirfd@ argument to @openat@.-               -> FilePath  -- ^ Path to open.-               -> OpenPathFlags  -- ^ Flag settings to pass.-               -> (Fd -> m a)  -- ^ Action to call with a file descriptor to the given path.-               -> m a  -- ^ Result of the invoked action.-withOpenPathAt dirfd pathname flags = bracket (liftIO $ openat dirfd pathname flags') (liftIO . closeFd)-  where-    flags' = foldr (.|.) 0 [ #{const O_PATH}-                           , #{const O_RDONLY}-                           , if directory flags then #{const O_DIRECTORY} else 0-                           , if nofollow flags then #{const O_NOFOLLOW} else 0-                           , if cloexec flags then #{const O_CLOEXEC} else 0-                           ]
+ internal/System/Landlock/Rules.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE StandaloneDeriving #-}++module System.Landlock.Rules+  ( Rule,+    RuleType (..),+    ruleType,+    pathBeneath,+    AccessFsFlag (..),+  )+where++import Foreign.Storable (Storable (..))+import System.Landlock.Flags+  ( AccessFsFlag (..),+    accessFsFlagToBit,+    fromBits,+    toBits,+  )+import System.Landlock.Hsc+  ( Landlock_rule_type,+    lANDLOCK_RULE_PATH_BENEATH,+    landlock_path_beneath_attr_alignment,+    landlock_path_beneath_attr_peek_allowed_access,+    landlock_path_beneath_attr_peek_parent_fd,+    landlock_path_beneath_attr_poke_allowed_access,+    landlock_path_beneath_attr_poke_parent_fd,+    landlock_path_beneath_attr_size,+  )+import System.Posix.Types (Fd)++-- | Kind of 'Rule's.+data RuleType = PathBeneath++-- | A rule enforced by Landlock, to be registered using 'System.Landlock.addRule'.+--+-- 'Rule's can be constructed using the relevant functions, like 'pathBeneath'.+data Rule (a :: RuleType) where+  PathBeneathRule :: [AccessFsFlag] -> Fd -> Rule 'PathBeneath++deriving instance Show (Rule a)++deriving instance Eq (Rule a)++-- | Retrieve the @enum landlock_rule_type@ value of a given 'Rule'.+ruleType :: Rule a -> Landlock_rule_type+ruleType = \case+  PathBeneathRule {} -> lANDLOCK_RULE_PATH_BENEATH++instance Storable (Rule 'PathBeneath) where+  sizeOf _ = landlock_path_beneath_attr_size+  alignment _ = landlock_path_beneath_attr_alignment+  peek ptr = do+    allowedAccess <- landlock_path_beneath_attr_peek_allowed_access ptr+    parentFd <- landlock_path_beneath_attr_peek_parent_fd ptr+    return $+      PathBeneathRule+        (fromBits accessFsFlagToBit allowedAccess)+        (fromIntegral parentFd)+  poke ptr (PathBeneathRule flags fd) = do+    let allowedAccess = toBits accessFsFlagToBit flags+        parentFd = fromIntegral fd+    landlock_path_beneath_attr_poke_allowed_access ptr allowedAccess+    landlock_path_beneath_attr_poke_parent_fd ptr parentFd++-- | Construct a path hierarchy rule definition.+--+-- This corresponds to a rule of type @LANDLOCK_RULE_PATH_BENEATH@, with+-- attributes defined in a @struct landlock_path_beneath_attr@.+pathBeneath ::+  -- | File descriptor, preferably opened with @O_PATH@, which+  --   identifies the parent directory of a file hierarchy, or+  --   just a file.+  Fd ->+  -- | Allowed actions for this file hierarchy+  --   (cf. 'AccessFsFlag').+  [AccessFsFlag] ->+  Rule 'PathBeneath+pathBeneath fd flags = PathBeneathRule flags fd
− internal/System/Landlock/Rules.hsc
@@ -1,66 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE StandaloneDeriving #-}--module System.Landlock.Rules (-      Rule-    , RuleType(..)-    , ruleType-    , pathBeneath-    , AccessFsFlag(..)-    ) where--#include <linux/landlock.h>-#include <sys/types.h>--import Data.Int (Int32)-import Data.Word (Word32, Word64)-import Foreign.Storable (Storable(..))-import System.Posix.Types (Fd)--import System.Landlock.Flags (AccessFsFlag(..), accessFsFlagToBit, fromBits, toBits)---- | Kind of 'Rule's.-data RuleType = PathBeneath---- | A rule enforced by Landlock, to be registered using 'System.Landlock.addRule'.------ 'Rule's can be constructed using the relevant functions, like 'pathBeneath'.-data Rule (a :: RuleType) where-    PathBeneathRule :: [AccessFsFlag] -> Fd -> Rule 'PathBeneath--deriving instance Show (Rule a)-deriving instance Eq (Rule a)---- | Retrieve the @enum landlock_rule_type@ value of a given 'Rule'.-ruleType :: Rule a -> #{type enum landlock_rule_type}-ruleType = \case-    PathBeneathRule{} -> #{const LANDLOCK_RULE_PATH_BENEATH}--instance Storable (Rule 'PathBeneath) where-    sizeOf _ = #{size struct landlock_path_beneath_attr}-    alignment _ = #{alignment struct landlock_path_beneath_attr}-    peek ptr = do-        allowedAccess <- #{peek struct landlock_path_beneath_attr, allowed_access} ptr :: IO #{type __u64}-        parentFd <- #{peek struct landlock_path_beneath_attr, parent_fd} ptr :: IO #{type __s32}-        return $ PathBeneathRule (fromBits allowedAccess accessFsFlagToBit) (fromIntegral parentFd)-    poke ptr (PathBeneathRule flags fd) = do-        let allowedAccess = toBits flags accessFsFlagToBit :: #{type __u64}-            parentFd = fromIntegral fd :: #{type __s32}-        #{poke struct landlock_path_beneath_attr, allowed_access} ptr allowedAccess-        #{poke struct landlock_path_beneath_attr, parent_fd} ptr parentFd---- | Construct a path hierarchy rule definition.------ This corresponds to a rule of type @LANDLOCK_RULE_PATH_BENEATH@, with--- attributes defined in a @struct landlock_path_beneath_attr@.-pathBeneath :: Fd  -- ^ File descriptor, preferably opened with @O_PATH@, which-                   --   identifies the parent directory of a file hierarchy, or-                   --   just a file.-            -> [AccessFsFlag]  -- ^ Allowed actions for this file hierarchy-                               --   (cf. 'AccessFsFlag').-            -> Rule 'PathBeneath-pathBeneath fd flags = PathBeneathRule flags fd
+ internal/System/Landlock/Syscalls.hs view
@@ -0,0 +1,127 @@+{-# LANGUAGE CApiFFI #-}++module System.Landlock.Syscalls+  ( LandlockRulesetAttr (..),+    landlock_create_ruleset,+    landlock_add_rule,+    landlock_restrict_self,+    prctl,+    pR_SET_NO_NEW_PRIVS,+    throwIfNonZero,+  )+where++import Control.Monad (unless)+import Foreign.C.Error (throwErrnoIfMinus1)+import Foreign.C.Types (CInt (..), CLong (..), CSize (..), CULong (..))+import Foreign.Ptr (Ptr)+import Foreign.Storable (Storable (..))+import System.IO.Error (ioeSetLocation)+import System.Landlock.Hsc+  ( Landlock_rule_type,+    U32,+    U64,+    landlock_ruleset_attr_alignment,+    landlock_ruleset_attr_peek_handled_access_fs,+    landlock_ruleset_attr_poke_handled_access_fs,+    landlock_ruleset_attr_size,+    pR_SET_NO_NEW_PRIVS,+  )++{- HLINT ignore LandlockRulesetAttr "Use newtype instead of data" -}+data LandlockRulesetAttr = LandlockRulesetAttr+  { landlockRulesetAttrHandledAccessFs :: U64+  }+  deriving (Show, Eq)++instance Storable LandlockRulesetAttr where+  sizeOf _ = landlock_ruleset_attr_size+  alignment _ = landlock_ruleset_attr_alignment+  peek ptr =+    LandlockRulesetAttr+      <$> landlock_ruleset_attr_peek_handled_access_fs ptr+  poke ptr attr =+    landlock_ruleset_attr_poke_handled_access_fs+      ptr+      (landlockRulesetAttrHandledAccessFs attr)++foreign import capi unsafe "hs-landlock.h hs_landlock_create_ruleset"+  _landlock_create_ruleset ::+    Ptr LandlockRulesetAttr ->+    CSize ->+    U32 ->+    IO CLong++{- HLINT ignore landlock_create_ruleset "Use camelCase" -}+landlock_create_ruleset ::+  Ptr LandlockRulesetAttr ->+  CSize ->+  U32 ->+  IO CLong+landlock_create_ruleset attr size flags =+  throwErrnoIfMinus1 "landlock_create_ruleset" $+    _landlock_create_ruleset attr size flags++foreign import capi unsafe "hs-landlock.h hs_landlock_add_rule"+  _landlock_add_rule ::+    CInt ->+    Landlock_rule_type ->+    Ptr a ->+    U32 ->+    IO CLong++{- HLINT ignore landlock_add_rule "Use camelCase" -}+landlock_add_rule ::+  CInt ->+  Landlock_rule_type ->+  Ptr a ->+  U32 ->+  IO ()+landlock_add_rule ruleset_fd rule_type rule_attr flags =+  throwIfNonZero "landlock_add_rule" $+    throwErrnoIfMinus1 "landlock_add_rule" $+      _landlock_add_rule ruleset_fd rule_type rule_attr flags++foreign import capi unsafe "hs-landlock.h hs_landlock_restrict_self"+  _landlock_restrict_self ::+    CInt ->+    U32 ->+    IO CLong++{- HLINT ignore landlock_restrict_self "Use camelCase" -}+landlock_restrict_self ::+  CInt ->+  U32 ->+  IO ()+landlock_restrict_self ruleset_fd flags =+  throwIfNonZero "landlock_restrict_self" $+    throwErrnoIfMinus1 "landlock_restrict_self" $+      _landlock_restrict_self ruleset_fd flags++foreign import capi unsafe "hs-landlock.h hs_landlock_prctl"+  _prctl ::+    CInt ->+    CULong ->+    CULong ->+    CULong ->+    CULong ->+    IO CInt++prctl ::+  CInt ->+  CULong ->+  CULong ->+  CULong ->+  CULong ->+  IO CInt+prctl option arg2 arg3 arg4 arg5 =+  throwErrnoIfMinus1 "prctl" $ _prctl option arg2 arg3 arg4 arg5++throwIfNonZero :: (Num a, Eq a, Show a) => String -> IO a -> IO ()+throwIfNonZero location act = do+  rc <- act+  unless (rc == 0) $+    ioError $+      flip ioeSetLocation location $+        userError $+          "Unexpected return value: " ++ show rc ++ " /= 0"
− internal/System/Landlock/Syscalls.hsc
@@ -1,86 +0,0 @@-module System.Landlock.Syscalls (-      LandlockRulesetAttr(..)-    , landlock_create_ruleset-    , landlock_add_rule-    , landlock_restrict_self-    , prctl-    ) where--#include <stddef.h>-#include <linux/landlock.h>-#include <sys/prctl.h>-#include <sys/types.h>--#include "hs-landlock.h"--import Data.Int (Int32, Int64)-import Data.Word (Word32, Word64)-import Foreign.C.Error (throwErrnoIfMinus1)-import Foreign.Ptr (Ptr)-import Foreign.Storable (Storable(..))--data LandlockRulesetAttr = LandlockRulesetAttr { landlockRulesetAttrHandledAccessFs :: #{type __u64} }-  deriving (Show, Eq)--instance Storable LandlockRulesetAttr where-  sizeOf _ = #{size struct landlock_ruleset_attr}-  alignment _ = #{alignment struct landlock_ruleset_attr}-  peek ptr = LandlockRulesetAttr <$> #{peek struct landlock_ruleset_attr, handled_access_fs} ptr-  poke ptr attr = do-      #{poke struct landlock_ruleset_attr, handled_access_fs} ptr (landlockRulesetAttrHandledAccessFs attr)--foreign import ccall unsafe "hs-landlock.h landlock_create_ruleset"-  _landlock_create_ruleset :: Ptr LandlockRulesetAttr-                           -> #{type size_t}-                           -> #{type __u32}-                           -> IO #{type long}--landlock_create_ruleset :: Ptr LandlockRulesetAttr-                        -> #{type size_t}-                        -> #{type __u32}-                        -> IO #{type long}-landlock_create_ruleset attr size flags =-    throwErrnoIfMinus1 "landlock_create_ruleset" $ _landlock_create_ruleset attr size flags--foreign import ccall unsafe "hs-landlock.h landlock_add_rule"-  _landlock_add_rule :: #{type int}-                     -> #{type enum landlock_rule_type}-                     -> Ptr a-                     -> #{type __u32}-                     -> IO #{type long}--landlock_add_rule :: #{type int}-                  -> #{type enum landlock_rule_type}-                  -> Ptr a-                  -> #{type __u32}-                  -> IO #{type long}-landlock_add_rule ruleset_fd rule_type rule_attr flags =-    throwErrnoIfMinus1 "landlock_add_rule" $ _landlock_add_rule ruleset_fd rule_type rule_attr flags--foreign import ccall unsafe "hs-landlock.h landlock_restrict_self"-  _landlock_restrict_self :: #{type int}-                          -> #{type __u32}-                          -> IO #{type long}--landlock_restrict_self :: #{type int}-                       -> #{type __u32}-                       -> IO #{type long}-landlock_restrict_self ruleset_fd flags =-    throwErrnoIfMinus1 "landlock_restrict_self" $ _landlock_restrict_self ruleset_fd flags--foreign import ccall unsafe "hs-landlock.h hs_landlock_prctl"-  _prctl :: #{type int}-        -> #{type unsigned long}-        -> #{type unsigned long}-        -> #{type unsigned long}-        -> #{type unsigned long}-        -> IO #{type int}--prctl :: #{type int}-      -> #{type unsigned long}-      -> #{type unsigned long}-      -> #{type unsigned long}-      -> #{type unsigned long}-      -> IO #{type int}-prctl option arg2 arg3 arg4 arg5 =-    throwErrnoIfMinus1 "prctl" $ _prctl option arg2 arg3 arg4 arg5
+ internal/System/Landlock/Version.hs view
@@ -0,0 +1,28 @@+module System.Landlock.Version+  ( Version (..),+    version1,+    version2,+    version3,+  )+where++-- | Representation of a Landlock ABI version as reported by the kernel.+newtype Version = Version+  { -- | Get the numerical version.+    getVersion :: Word+  }+  deriving (Show, Eq, Ord)++-- All ABI versions supported by this library should be exposed as a value.++-- | ABI version 1.+version1 :: Version+version1 = Version 1++-- | ABI version 2.+version2 :: Version+version2 = Version 2++-- | ABI version 3.+version3 :: Version+version3 = Version 3
− internal/System/Landlock/Version.hsc
@@ -1,15 +0,0 @@-module System.Landlock.Version (-      Version(..)-    , version1-    ) where--import Data.Int (Int64)---- | Representation of a Landlock ABI version as reported by the kernel.-newtype Version = Version { getVersion :: #{type long} }-  deriving (Show, Eq, Ord)---- All ABI versions supported by this library should be exposed as a value.--- | ABI version 1.-version1 :: Version-version1 = Version 1
landlock.cabal view
@@ -1,10 +1,9 @@-Cabal-Version:       2.2-Build-Type:          Simple--Name:                landlock-Version:             0.2.0.1-Synopsis:            Haskell bindings for the Linux Landlock API-Description:+cabal-version:      2.2+build-type:         Simple+name:               landlock+version:            0.2.1.0+synopsis:           Haskell bindings for the Linux Landlock API+description:   This library exposes Haskell bindings for the Linux kernel Landlock API.   .   The Linux kernel Landlock API provides unprivileged access control. The goal@@ -18,105 +17,197 @@   .   For more information, see the <https://landlock.io/ Landlock homepage> and its   <https://docs.kernel.org/userspace-api/landlock.html kernel documentation>.-Homepage:            https://github.com/NicolasT/landlock-hs-Bug-Reports:         https://github.com/NicolasT/landlock-hs/issues-License:             BSD-3-Clause-License-File:        LICENSE-Author:              Nicolas Trangez-Maintainer:          ikke@nicolast.be-Copyright:           (c) 2022 Nicolas Trangez-Category:            System-Stability:           alpha -Extra-Source-Files:+homepage:           https://github.com/NicolasT/landlock-hs+bug-reports:        https://github.com/NicolasT/landlock-hs/issues+license:            BSD-3-Clause+license-file:       LICENSE+author:             Nicolas Trangez+maintainer:         ikke@nicolast.be+copyright:          (c) 2022 Nicolas Trangez+category:           System+stability:          alpha+extra-doc-files:+  cbits/linux/GPL-2.0+  cbits/linux/Linux-syscall-note   CHANGELOG.md   README.md++extra-source-files:   cbits/hs-landlock.h+  cbits/linux/landlock.h -Tested-With:         GHC ==8.10.5-                   , GHC ==9.0.2-                   , GHC ==9.2.2+tested-with:        GHC ==8.10.7 || ==9.0.2 || ==9.2.4 || ==9.4.2 -Source-Repository head-  Type:                git-  Location:            https://github.com/NicolasT/landlock-hs.git-  Subdir:              landlock-  Branch:              main+source-repository head+  type:     git+  location: https://github.com/NicolasT/landlock-hs.git+  subdir:   landlock+  branch:   main -Library-  Exposed-Modules:     System.Landlock-  Build-Depends:       landlock-internal-                     , base ^>=4.14.2.0 || ^>=4.15 || ^>=4.16-                     , exceptions ^>=0.10.4-                     , unix ^>=2.7.2.2-  Build-Tool-Depends:  hsc2hs:hsc2hs-  Hs-Source-Dirs:      src-  Default-Language:    Haskell2010-  Other-Extensions:    EmptyCase-                       EmptyDataDeriving-                       FlexibleContexts-                       LambdaCase-                       RankNTypes-  Ghc-Options:         -Wall+flag landlocked+  description: Build the landlocked utility.+  default:     True+  manual:      True -Library landlock-internal-  Exposed-Modules:     System.Landlock.Flags-                     , System.Landlock.OpenPath-                     , System.Landlock.Rules-                     , System.Landlock.Syscalls-                     , System.Landlock.Version-  Include-Dirs:        cbits-  C-Sources:           cbits/hs-landlock.c-  Cc-Options:          -Wall-  Build-Depends:       psx ^>=0.1-                     , base-                     , exceptions-                     , unix-  Build-Tool-Depends:  hsc2hs:hsc2hs-  Hs-Source-Dirs:      internal-  Default-Language:    Haskell2010-  Other-Extensions:    DataKinds-                       FlexibleInstances-                       GADTs-                       KindSignatures-                       LambdaCase-                       StandaloneDeriving-  Ghc-Options:         -Wall+flag werror+  description: Turn compiler warnings into errors.+  default:     False+  manual:      True -Test-Suite landlock-test-  Type:                exitcode-stdio-1.0-  Hs-Source-Dirs:      test-  Main-Is:             landlock-test.hs-  Other-Modules:       ThreadedScenario-  Build-Depends:       landlock-                     , landlock-internal-                     , base-                     , async ^>=2.2.3-                     , filepath ^>=1.4.2.1-                     , process ^>=1.6.9.0-                     , QuickCheck ^>=2.14.2-                     , tasty ^>=1.4.1-                     , tasty-expected-failure ^>=0.12.3-                     , tasty-hunit ^>=0.10.0.3-                     , tasty-quickcheck ^>=0.10.1.2-  Default-Language:    Haskell2010-  Other-Extensions:    DataKinds-                       FlexibleInstances-                       LambdaCase-                       ScopedTypeVariables-                       TypeApplications-  Ghc-Options:         -Wall+common common-settings+  default-language: Haskell2010+  ghc-options:+    -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates+    -Wpartial-fields -Wmissing-home-modules -Widentities+    -Wredundant-constraints -Wcpp-undef -Wmissing-export-lists -Test-Suite landlock-test-threaded-  Type:                exitcode-stdio-1.0-  Hs-Source-Dirs:      test-  Main-Is:             landlock-test-threaded.hs-  Other-Modules:       ThreadedScenario-  Build-Depends:       landlock-                     , base-                     , async ^>=2.2.3-                     , tasty ^>=1.4.1-                     , tasty-expected-failure ^>=0.12.3-                     , tasty-hunit ^>=0.10.0.3-  Default-Language:    Haskell2010-  Ghc-Options:         -Wall -threaded -with-rtsopts -N2+  cc-options:       -Wall -Wextra -pedantic++  if flag(werror)+    ghc-options: -Werror -optc=-Werror++    -- Note: disable some warnings triggered by `hsc2hs` code+    cc-options:+      -Werror -Wno-error=overlength-strings -Wno-error=type-limits+      -Wno-error=variadic-macros++library+  import:           common-settings+  exposed-modules:  System.Landlock+  build-depends:+    , base               >=4.14.2.0 && <4.17 || ^>=4.17+    , exceptions         ^>=0.10.4+    , landlock-internal+    , unix               >=2.7.2.2  && <2.8  || ^>=2.8++  hs-source-dirs:   src+  other-extensions:+    FlexibleContexts+    RankNTypes++library landlock-internal+  import:             common-settings+  exposed-modules:+    System.Landlock.Flags+    System.Landlock.Hsc+    System.Landlock.OpenPath+    System.Landlock.Rules+    System.Landlock.Syscalls+    System.Landlock.Version++  include-dirs:       cbits+  c-sources:          cbits/hs-landlock.c+  build-depends:+    , base        >=4.14.2.0 && <4.17 || ^>=4.17+    , exceptions  ^>=0.10.4+    , psx         ^>=0.1+    , unix        >=2.7.2.2  && <2.8  || ^>=2.8++  build-tool-depends: hsc2hs:hsc2hs+  hs-source-dirs:     internal+  other-extensions:+    CApiFFI+    DataKinds+    EmptyCase+    EmptyDataDeriving+    FlexibleInstances+    GADTs+    KindSignatures+    LambdaCase+    StandaloneDeriving++executable landlocked+  import:          common-settings++  if !flag(landlocked)+    buildable: False++  main-is:         landlocked.hs+  other-modules:   Paths_landlock+  autogen-modules: Paths_landlock+  hs-source-dirs:  bin+  build-depends:+    , base                  >=4.14.2.0 && <4.17 || ^>=4.17+    , exceptions            ^>=0.10.4+    , landlock+    , optparse-applicative  >=0.16.1.0 && <0.17 || ^>=0.17+    , unix                  >=2.7.2.2  && <2.8  || ^>=2.8++test-suite landlock-test+  import:           common-settings+  type:             exitcode-stdio-1.0+  hs-source-dirs:   test+  main-is:          landlock-test.hs+  other-modules:    ThreadedScenario+  build-depends:+    , async                    ^>=2.2.3+    , base                     >=4.14.2.0  && <4.17 || ^>=4.17+    , filepath                 ^>=1.4.2.1+    , landlock+    , landlock-internal+    , process                  ^>=1.6.9.0+    , QuickCheck               ^>=2.14.2+    , quickcheck-classes-base  ^>=0.6.2.0+    , tasty                    ^>=1.4.1+    , tasty-hunit              ^>=0.10.0.3+    , tasty-quickcheck         ^>=0.10.1.2++  other-extensions:+    CApiFFI+    DataKinds+    FlexibleInstances+    LambdaCase+    TypeApplications++test-suite landlock-test-threaded+  import:           common-settings+  type:             exitcode-stdio-1.0+  hs-source-dirs:   test+  main-is:          landlock-test-threaded.hs+  other-modules:    ThreadedScenario+  build-depends:+    , async        ^>=2.2.3+    , base         >=4.14.2.0  && <4.17 || ^>=4.17+    , landlock+    , tasty        ^>=1.4.1+    , tasty-hunit  ^>=0.10.0.3++  ghc-options:      -threaded -with-rtsopts -N2+  other-extensions: CApiFFI++test-suite landlock-readme+  import:             common-settings+  type:               exitcode-stdio-1.0+  main-is:            README.lhs+  other-modules:      ReadmeUtils+  hs-source-dirs:     . test+  build-depends:+    , base       >=4.14.2.0 && <4.17 || ^>=4.17+    , directory  ^>=1.3.6.0+    , filepath   ^>=1.4.2.1+    , landlock+    , process    ^>=1.6.9.0+    , temporary  ^>=1.3++  build-tool-depends: markdown-unlit:markdown-unlit+  ghc-options:        -pgmL markdown-unlit++test-suite landlocked-test+  import:             common-settings++  if !flag(landlocked)+    buildable: False++  type:               exitcode-stdio-1.0+  hs-source-dirs:     test+  main-is:            landlocked-test.hs+  build-depends:+    , base         >=4.14.2.0  && <4.17 || ^>=4.17+    , filepath     ^>=1.4.2.1+    , process      ^>=1.6.9.0+    , tasty        ^>=1.4.1+    , tasty-hunit  ^>=0.10.0.3+    , temporary    ^>=1.3++  build-tool-depends: landlock:landlocked
+ src/System/Landlock.hs view
@@ -0,0 +1,263 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}++-- |+-- Module:      System.Landlock+-- Description: Haskell bindings for the Linux kernel Landlock API+-- Copyright:   (c) Nicolas Trangez, 2022+-- License:     BSD-3-Clause+-- Maintainer:  ikke@nicolast.be+-- Stability:   alpha+-- Portability: Linux+--+-- 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](https://landlock.io/) and its+-- [kernel documentation](https://docs.kernel.org/userspace-api/landlock.html).+module System.Landlock+  ( -- * Core API++    --++    -- | Use 'landlock' to sandbox a process.+    -- $example+    landlock,+    RulesetAttr (..),++    -- ** Filesystem Access Flags++    --++    -- | Filesystem access flags to sandbox filesystem access.+    AccessFsFlag (..),+    accessFsFlags,+    accessFsFlagIsReadOnly,++    -- * Sandboxing Rules++    --++    -- | Sandboxing rules to apply.+    Rule,+    pathBeneath,++    -- * Utility Functions++    --++    -- | Various utility functions.+    isSupported,++    -- ** Landlock ABI Version++    --++    -- | Retrieve and handle the kernel's Landlock ABI version.+    abiVersion,+    Version,+    getVersion,+    version1,+    version2,+    version3,++    -- ** Opening paths using @O_PATH@++    --++    -- | When creating a 'pathBeneath' rule, a file descriptor to a directory+    -- or file is needed. These can be safely @open@ed using the @O_PATH@ flag+    -- using the following functions.+    withOpenPath,+    withOpenPathAt,+    OpenPathFlags (..),+    defaultOpenPathFlags,+  )+where++import Control.Exception.Base (handleJust)+import Control.Monad (void)+import Control.Monad.Catch (MonadMask, bracket)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Foreign.Marshal.Utils (with)+import Foreign.Ptr (nullPtr)+import Foreign.Storable (Storable, sizeOf)+import GHC.IO.Exception (IOErrorType (UnsupportedOperation))+import System.IO.Error (ioeGetErrorType)+import System.Landlock.Flags+  ( AccessFsFlag (..),+    AddRuleFlag,+    CreateRulesetFlag (..),+    RestrictSelfFlag,+    accessFsFlagIsReadOnly,+    accessFsFlagToBit,+    accessFsFlags,+    addRuleFlagToBit,+    createRulesetFlagToBit,+    restrictSelfFlagToBit,+    toBits,+  )+import System.Landlock.OpenPath (OpenPathFlags (..), defaultOpenPathFlags, withOpenPath, withOpenPathAt)+import System.Landlock.Rules (Rule, pathBeneath, ruleType)+import System.Landlock.Syscalls+  ( LandlockRulesetAttr (..),+    landlock_add_rule,+    landlock_create_ruleset,+    landlock_restrict_self,+    pR_SET_NO_NEW_PRIVS,+    prctl,+    throwIfNonZero,+  )+import System.Landlock.Version (Version (..), version1, version2, version3)+import System.Posix.IO (closeFd)+import System.Posix.Types (Fd)++-- | Retrieve the Landlock ABI version of the running system.+--+-- __Warning:__ calling this on a system without Landlock support, or with+-- Landlock disabled, will result in an exception.+abiVersion :: IO Version+abiVersion = Version . fromIntegral <$> landlock_create_ruleset nullPtr 0 flags+  where+    flags = toBits createRulesetFlagToBit [CreateRulesetVersion]++-- | Check whether Landlock is supported and enabled on the running system.+isSupported :: IO Bool+isSupported = handleJust unsupportedOperation (\() -> return False) $ do+  void abiVersion+  return True+  where+    unsupportedOperation exc = if isUnsupportedOperationError exc then Just () else Nothing+    isUnsupportedOperationError = isUnsupportedOperationErrorType . ioeGetErrorType+    isUnsupportedOperationErrorType = (== UnsupportedOperation)++-- | Ruleset attributes.+--+-- This represents a @struct landlock_ruleset_attr@ as passed to+-- @landlock_create_ruleset@.++{- HLINT ignore "Use newtype instead of data" -}+data RulesetAttr = RulesetAttr+  { -- | Actions (cf. 'AccessFsFlag') that ought to+    -- be handled by a ruleset and should be+    -- forbidden if no rule explicitly allow them.+    -- This is needed forbackward compatibility+    -- reasons.+    rulesetAttrHandledAccessFs :: [AccessFsFlag]+  }+  deriving (Show, Eq)++-- | Handle to a Landlock ruleset. Use 'addRule' to register new rules.+newtype LandlockFd = LandlockFd {unLandlockFd :: Fd}+  deriving (Show, Eq)++createRuleset :: RulesetAttr -> [CreateRulesetFlag] -> IO LandlockFd+createRuleset attr flags = with attr' $ \attrp -> wrap <$> landlock_create_ruleset attrp (fromIntegral $ sizeOf attr') flags'+  where+    wrap = LandlockFd . fromIntegral+    attr' = LandlockRulesetAttr {landlockRulesetAttrHandledAccessFs = handledAccessFs}+    handledAccessFs = toBits accessFsFlagToBit (rulesetAttrHandledAccessFs attr)+    flags' = toBits createRulesetFlagToBit flags++restrictSelf :: LandlockFd -> [RestrictSelfFlag] -> IO ()+restrictSelf fd flags =+  landlock_restrict_self (fromIntegral $ unLandlockFd fd) flags'+  where+    flags' = toBits restrictSelfFlagToBit flags++-- | Apply a Landlock sandbox to the current process.+--+-- The provided action can be used to register Landlock 'Rule's on the given+-- instance (see 'addRule').+--+-- Once this returns, the Landlock sandbox will be in effect (see+-- @landlock_restrict_self@), and no privileged processes can be spawned+-- (@prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)@ has been invoked).+--+-- __Warning:__ calling this on a system without Landlock support, or with+-- Landlock disabled, will result in an exception.+landlock ::+  (MonadMask m, MonadIO m) =>+  -- | Ruleset attribute passed to @landlock_create_ruleset@.+  RulesetAttr ->+  -- | Flags passed to @landlock_create_ruleset@. Since no flags but+  --   'CreateRulesetVersion' (@LANDLOCK_CREATE_RULESET_VERSION@) are+  --   defined, and this flag must not be used when creating an actual+  --   ruleset, this should be an empty list.+  [CreateRulesetFlag] ->+  -- | Flags passed to @landlock_restrict_self@. Since no flags are+  -- defined, this should be an empty list.+  [RestrictSelfFlag] ->+  -- | Action that will be called before the Landlock sandbox is+  -- enforced. The provided function can be used to register+  -- sandboxing rules (internally using @landlock_add_rule@),+  -- given a 'Rule' and a set of 'AddRuleFlag's. However, since no+  -- flags are currently defined, this should be an empty list.+  ((Storable (Rule r) => Rule r -> [AddRuleFlag] -> m ()) -> m a) ->+  -- | Result of the given action.+  m a+landlock attr createRulesetFlags restrictSelfFlags act =+  bracket (liftIO $ createRuleset attr createRulesetFlags) (liftIO . closeFd . unLandlockFd) $ \fd -> do+    res <- act (addRule fd)+    liftIO $ do+      throwIfNonZero "prtcl" $ prctl pR_SET_NO_NEW_PRIVS 1 0 0 0+      restrictSelf fd restrictSelfFlags+    return res++-- | Register a new 'Rule' with a Landlock instance.+addRule ::+  (MonadIO m, Storable (Rule a)) =>+  -- | Handle to a Landlock instance.+  LandlockFd ->+  -- | Sandboxing 'Rule' to register with the Landlock instance.+  Rule a ->+  -- | Flags. Since no flags are defined, this should be an empty+  --   list.+  [AddRuleFlag] ->+  m ()+addRule fd rule flags = liftIO $+  with rule $ \ruleAttrp ->+    landlock_add_rule (fromIntegral $ unLandlockFd fd) (ruleType rule) ruleAttrp flags'+  where+    flags' = toBits addRuleFlagToBit flags++-- $example+--+-- Example usage:+--+-- @+-- -- Retrieve the Landlock ABI version+-- abi <- abiVersion+--+-- -- Calculate access flag sets+-- -- Note: in production code, find the highest matching version or similar+-- let Just flags = lookup abi accessFsFlags+--     readOnlyFlags = filter accessFsFlagIsReadOnly flags+--+-- -- Sandbox the process+-- landlock (RulesetAttr flags) [] [] $ \\addRule -> do+--     -- Allow read-only access to the /usr hierarchy+--     withOpenPath "/usr" defaultOpenPathFlags{ directory = True } $ \\fd ->+--         addRule (pathBeneath fd readOnlyFlags) []+--+--     -- Allow read access to my public key+--     withOpenPath "\/home\/nicolas\/.ssh\/id_ed25519.pub" defaultOpenPathFlags $ \\fd ->+--         addRule (pathBeneath fd [AccessFsReadFile]) []+--+-- withFile "\/home\/nicolas\/.ssh\/id_ed25519.pub" ReadMode (\\fd -> putStrLn \"Success\")+-- -- Success+--+-- withFile "\/usr\/bin\/ghc" ReadMode (\\fd -> putStrLn \"Success\")+-- -- Success+--+-- openFile "\/home\/nicolas\/.ssh\/id_ed25519" ReadMode+-- -- *** Exception: \/home\/nicolas\/.ssh\/id_ed25519: openFile: permission denied (Permission denied)+-- @
− src/System/Landlock.hsc
@@ -1,249 +0,0 @@-{-# LANGUAGE EmptyCase #-}-{-# LANGUAGE EmptyDataDeriving #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RankNTypes #-}---- |--- Module:      System.Landlock--- Description: Haskell bindings for the Linux kernel Landlock API--- Copyright:   (c) Nicolas Trangez, 2022--- License:     BSD-3-Clause--- Maintainer:  ikke@nicolast.be--- Stability:   alpha--- Portability: Linux------ 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](https://landlock.io/) and its--- [kernel documentation](https://docs.kernel.org/userspace-api/landlock.html).--module System.Landlock (-    -- * Core API-    ---    -- | Use 'landlock' to sandbox a process.-    ---    -- $example-      landlock-    , RulesetAttr(..)-    -- ** Filesystem Access Flags-    ---    -- | Filesystem access flags to sandbox filesystem access.-    , AccessFsFlag(..)-    , accessFsFlags-    , accessFsFlagIsReadOnly-    -- * Sandboxing Rules-    ---    -- | Sandboxing rules to apply.-    , Rule-    , pathBeneath-    -- * Utility Functions-    ---    -- | Various utility functions.-    , isSupported-    -- ** Landlock ABI Version-    ---    -- | Retrieve and handle the kernel's Landlock ABI version.-    , abiVersion-    , Version-    , version1-    -- ** Opening paths using @O_PATH@-    ---    -- | When creating a 'pathBeneath' rule, a file descriptor to a directory-    -- or file is needed. These can be safely @open@ed using the @O_PATH@ flag-    -- using the following functions.-    , withOpenPath-    , withOpenPathAt-    , OpenPathFlags(..)-    , defaultOpenPathFlags-    ) where--#define _GNU_SOURCE--#include <linux/landlock.h>-#include <sys/prctl.h>--import Control.Exception.Base (handleJust)-import Control.Monad (void)-import Control.Monad.Catch (MonadMask, bracket)-import Control.Monad.IO.Class (MonadIO, liftIO)-import GHC.IO.Exception (IOErrorType(UnsupportedOperation))-import Foreign.Marshal.Utils (with)-import Foreign.Ptr (nullPtr)-import Foreign.Storable (Storable, sizeOf)-import System.IO.Error (ioeGetErrorType)-import System.Posix.IO (closeFd)-import System.Posix.Types (Fd)--import System.Landlock.Flags (AccessFsFlag(..), accessFsFlagToBit, accessFsFlags, accessFsFlagIsReadOnly, toBits)-import System.Landlock.OpenPath (OpenPathFlags(..), defaultOpenPathFlags, withOpenPath, withOpenPathAt)-import System.Landlock.Rules (Rule, ruleType, pathBeneath)-import System.Landlock.Syscalls (LandlockRulesetAttr(..), landlock_create_ruleset, landlock_add_rule, landlock_restrict_self, prctl)-import System.Landlock.Version (Version(..), version1)---- | Retrieve the Landlock ABI version of the running system.------ __Warning:__ calling this on a system without Landlock support, or with--- Landlock disabled, will result in an exception.-abiVersion :: IO Version-abiVersion = Version <$> landlock_create_ruleset nullPtr 0 #{const LANDLOCK_CREATE_RULESET_VERSION}---- | Check whether Landlock is supported and enabled on the running system.-isSupported :: IO Bool-isSupported = handleJust unsupportedOperation (\() -> return False) $ do-    void abiVersion-    return True-  where-    unsupportedOperation exc = if isUnsupportedOperationError exc then Just () else Nothing-    isUnsupportedOperationError = isUnsupportedOperationErrorType . ioeGetErrorType-    isUnsupportedOperationErrorType = (== UnsupportedOperation)---- | Ruleset attributes.------ This represents a @struct landlock_ruleset_attr@ as passed to--- @landlock_create_ruleset@.-data RulesetAttr = RulesetAttr { rulesetAttrHandledAccessFs :: [AccessFsFlag]-                                 -- ^ Actions (cf. 'AccessFsFlag') that ought to-                                 -- be handled by a ruleset and should be-                                 -- forbidden if no rule explicitly allow them.-                                 -- This is needed forbackward compatibility-                                 -- reasons.-                               }-  deriving (Show, Eq)---- | Flags passed to @landlock_create_ruleset@.------ In the current kernel API, only @LANDLOCK_CREATE_RULESET_VERSION@ is--- defined, which should not be used when creating an actual Landlock--- encironment (cf. 'landlock').-data CreateRulesetFlag = CreateRulesetVersion-  deriving (Show, Eq, Enum, Bounded)---- | Handle to a Landlock ruleset. Use 'addRule' to register new rules.-newtype LandlockFd = LandlockFd { unLandlockFd :: Fd }-  deriving (Show, Eq)--createRuleset :: RulesetAttr -> [CreateRulesetFlag] -> IO LandlockFd-createRuleset attr flags = with attr' $ \attrp -> wrap <$> landlock_create_ruleset attrp (fromIntegral $ sizeOf attr') flags'-  where-    wrap = LandlockFd . fromIntegral-    attr' = LandlockRulesetAttr { landlockRulesetAttrHandledAccessFs = handledAccessFs }-    handledAccessFs = toBits (rulesetAttrHandledAccessFs attr) accessFsFlagToBit-    flags' = toBits flags $ \case-       CreateRulesetVersion -> #{const LANDLOCK_CREATE_RULESET_VERSION}---- | Flags passed to @landlock_restrict_self@.------ In the current kernel API, no such flags are defined, hence this is a type--- without any constructors.-data RestrictSelfFlag-  deriving (Show, Eq)--restrictSelf :: LandlockFd -> [RestrictSelfFlag] -> IO ()-restrictSelf fd flags =-    void $ landlock_restrict_self (fromIntegral $ unLandlockFd fd) flags'-  where-    flags' = toBits flags $ \case {}---- | Apply a Landlock sandbox to the current process.------ The provided action can be used to register Landlock 'Rule's on the given--- instance (see 'addRule').------ Once this returns, the Landlock sandbox will be in effect (see--- @landlock_restrict_self@), and no privileged processes can be spawned--- (@prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)@ has been invoked).------ __Warning:__ calling this on a system without Landlock support, or with--- Landlock disabled, will result in an exception.-landlock :: (MonadMask m, MonadIO m)-         => RulesetAttr-            -- ^ Ruleset attribute passed to @landlock_create_ruleset@.-         -> [CreateRulesetFlag]-            -- ^ Flags passed to @landlock_create_ruleset@. Since no flags but-            --   'CreateRulesetVersion' (@LANDLOCK_CREATE_RULESET_VERSION@) are-            --   defined, and this flag must not be used when creating an actual-            --   ruleset, this should be an empty list.-         -> [RestrictSelfFlag]-            -- ^ Flags passed to @landlock_restrict_self@. Since no flags are-            -- defined, this should be an empty list.-         -> ((Storable (Rule r) => Rule r -> [AddRuleFlag] -> m ()) -> m a)-            -- ^ Action that will be called before the Landlock sandbox is-            -- enforced. The provided function can be used to register-            -- sandboxing rules (internally using @landlock_add_rule@),-            -- given a 'Rule' and a set of 'AddRuleFlag's. However, since no-            -- flags are currently defined, this should be an empty list.-         -> m a-            -- ^ Result of the given action.-landlock attr createRulesetFlags restrictSelfFlags act =-    bracket (liftIO $ createRuleset attr createRulesetFlags) (liftIO . closeFd . unLandlockFd) $ \fd -> do-        res <- act (addRule fd)-        liftIO $ do-            void $ prctl #{const PR_SET_NO_NEW_PRIVS} 1 0 0 0-            restrictSelf fd restrictSelfFlags-        return res---- | Flags passed to @landlock_add_rule@.------ In the current kernel API, no such flags are defined, hence this is a type--- without any constructors.-data AddRuleFlag-  deriving (Show, Eq)---- | Register a new 'Rule' with a Landlock instance.-addRule :: (MonadIO m, Storable (Rule a))-        => LandlockFd-           -- ^ Handle to a Landlock instance.-        -> Rule a-           -- ^ Sandboxing 'Rule' to register with the Landlock instance.-        -> [AddRuleFlag]-           -- ^ Flags. Since no flags are defined, this should be an empty-           --   list.-        -> m ()-addRule fd rule flags = liftIO $ with rule $ \ruleAttrp ->-    void $ landlock_add_rule (fromIntegral $ unLandlockFd fd) (ruleType rule) ruleAttrp flags'-  where-    flags' = toBits flags $ \case {}---- |--- $example------ Example usage:------ @--- -- Retrieve the Landlock ABI version--- abi <- abiVersion------ -- Calculate access flag sets--- -- Note: in production code, find the highest matching version or similar--- let Just flags = lookup abi accessFsFlags---     readOnlyFlags = filter accessFsFlagIsReadOnly flags------ -- Sandbox the process--- landlock (RulesetAttr flags) [] [] $ \\addRule -> do---     -- Allow read-only access to the /usr hierarchy---     withOpenPath "/usr" defaultOpenPathFlags{ directory = True } $ \\fd ->---         addRule (pathBeneath fd readOnlyFlags) []------     -- Allow read access to my public key---     withOpenPath "\/home\/nicolas\/.ssh\/id_ed25519.pub" defaultOpenPathFlags $ \\fd ->---         addRule (pathBeneath fd [AccessFsReadFile]) []------ withFile "\/home\/nicolas\/.ssh\/id_ed25519.pub" ReadMode (\\fd -> putStrLn \"Success\")--- -- Success------ withFile "\/usr\/bin\/ghc" ReadMode (\\fd -> putStrLn \"Success\")--- -- Success------ openFile "\/home\/nicolas\/.ssh\/id_ed25519" ReadMode--- -- *** Exception: \/home\/nicolas\/.ssh\/id_ed25519: openFile: permission denied (Permission denied)--- @
+ test/ReadmeUtils.hs view
@@ -0,0 +1,53 @@+module ReadmeUtils+  ( withFakeRoot,+    -- Control.Exception.Base+    handleJust,+    -- Control.Monad+    unless,+    -- System.Directory+    removeFile,+    -- System.FilePath+    (</>),+    -- System.IO+    IOMode (..),+    hPutStrLn,+    withFile,+    writeFile,+    -- System.IO.Error+    isPermissionError,+    -- System.Process+    readProcess,+  )+where++import Control.Exception.Base (handleJust)+import Control.Monad (unless)+import System.Directory (createDirectory, removeFile)+import System.FilePath ((</>))+import System.IO (IOMode (..), hPutStrLn, withFile)+import System.IO.Error (isPermissionError)+import System.IO.Temp (withSystemTempDirectory)+import System.Process (readProcess)++withFakeRoot :: (FilePath -> IO a) -> IO a+withFakeRoot fn = withSystemTempDirectory "landlock-readme" $ \tmp -> do+  let homeDir = tmp </> "home"+      userDir = homeDir </> "user"+      sshDir = userDir </> ".ssh"+      tmpDir = tmp </> "tmp"++  mapM_+    createDirectory+    [ homeDir,+      userDir,+      sshDir,+      tmpDir+    ]++  let privateKey = sshDir </> "id_ed25519"+      publicKey = sshDir </> "id_ed25519.pub"++  writeFile privateKey "private"+  writeFile publicKey "public"++  fn tmp
test/ThreadedScenario.hs view
@@ -1,68 +1,68 @@+{-# LANGUAGE CApiFFI #-}+ module ThreadedScenario (scenario) where  import Control.Concurrent.Async (Async, wait)-import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)+import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar) import Control.Exception.Base (handleJust)-import System.IO (IOMode(ReadMode), withFile)+import System.IO (IOMode (ReadMode), withFile) import System.IO.Error (isPermissionError)-import System.Posix.Types (CPid(..))-+import System.Landlock (AccessFsFlag (..), RulesetAttr (..), landlock)+import System.Posix.Types (CPid (..)) import Test.Tasty (TestTree) import Test.Tasty.HUnit (assertFailure, testCaseSteps) -import System.Landlock (AccessFsFlag(..), RulesetAttr(..), landlock)--foreign import ccall unsafe "unistd.h gettid"-    gettid :: IO CPid+foreign import capi unsafe "unistd.h gettid"+  gettid :: IO CPid  scenario :: (IO () -> (Async () -> IO ()) -> IO ()) -> TestTree scenario fn = testCaseSteps "Multithreaded Scenario" (scenario' fn)  scenario' :: (IO () -> (Async () -> IO ()) -> IO ()) -> (String -> IO ()) -> IO () scenario' withAsync step = do-    step "Starting scenario"--    mainTid <- gettid-    step $ "Main TID = " ++ show mainTid+  step "Starting scenario" -    tidMVar <- newEmptyMVar-    continueMVar <- newEmptyMVar+  mainTid <- gettid+  step $ "Main TID = " ++ show mainTid -    step "Launching thread"-    withAsync (thread tidMVar continueMVar) $ \child -> do-        _ <- takeMVar tidMVar+  tidMVar <- newEmptyMVar+  continueMVar <- newEmptyMVar -        step "Setting up Landlock sandbox"-        let flags = [AccessFsReadFile]-        landlock (RulesetAttr flags) [] [] $ \_ -> return ()+  step "Launching thread"+  withAsync (thread tidMVar continueMVar) $ \child -> do+    _ <- takeMVar tidMVar -        step "Assert file not readable from main thread"-        assertFileNotReadable "main"-        step "Success"+    step "Setting up Landlock sandbox"+    let flags = [AccessFsReadFile]+    landlock (RulesetAttr flags) [] [] $ \_ -> return () -        step "Letting thread continue"-        putMVar continueMVar ()+    step "Assert file not readable from main thread"+    assertFileNotReadable "main"+    step "Success" -        step "Waiting for thread to exit"-        wait child+    step "Letting thread continue"+    putMVar continueMVar () +    step "Waiting for thread to exit"+    wait child   where     thread tidMVar continueMVar = do-        step "Running in thread"+      step "Running in thread" -        tid <- gettid-        step $ "Thread TID = " ++ show tid-        putMVar tidMVar tid+      tid <- gettid+      step $ "Thread TID = " ++ show tid+      putMVar tidMVar tid -        step "Waiting for the signal..."-        () <- takeMVar continueMVar-        step "Received signal, continuing"+      step "Waiting for the signal..."+      () <- takeMVar continueMVar+      step "Received signal, continuing" -        step "Assert file not readable from thread"-        assertFileNotReadable "thread"-        step "Success"+      step "Assert file not readable from thread"+      assertFileNotReadable "thread"+      step "Success"      file = "/etc/resolv.conf"-    assertFileNotReadable env = handleJust permissionError return $ withFile file ReadMode $ \_ ->+    assertFileNotReadable env = handleJust permissionError return $+      withFile file ReadMode $ \_ ->         assertFailure $ "Still able to open file " ++ file ++ " in " ++ env     permissionError exc = if isPermissionError exc then Just () else Nothing
test/landlock-test-threaded.hs view
@@ -1,20 +1,13 @@ module Main (main) where  import Control.Concurrent.Async (withAsyncBound)- import Test.Tasty (defaultMain, testGroup)-import Test.Tasty.ExpectedFailure (expectFailBecause)--import System.Landlock (isSupported)- import ThreadedScenario (scenario)  main :: IO () main = do-    supported <- isSupported-    let scenario' = scenario withAsyncBound-    defaultMain $ testGroup "Threaded" [-          if supported-            then scenario'-            else expectFailBecause "Landlock not supported" scenario'-        ]+  defaultMain $+    testGroup+      "Threaded"+      [ scenario withAsyncBound+      ]
test/landlock-test.hs view
@@ -1,9 +1,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}- {-# OPTIONS_GHC -Wno-orphans #-}  module Main (main) where@@ -11,28 +9,49 @@ import Control.Concurrent.Async (withAsync) import Control.Exception.Base (handleJust) import Control.Monad (unless)-import Data.List (nub, sort)-import Data.Proxy (Proxy(Proxy))-import Foreign.Marshal.Alloc (alloca)-import Foreign.Storable (Storable, peek, poke)+import Data.List (nub, sort, (\\))+import Data.Proxy (Proxy (Proxy)) import System.Environment (lookupEnv)-import System.Exit (ExitCode(..))+import System.Exit (ExitCode (..)) import System.FilePath ((</>))-import System.IO (IOMode(..), withFile)+import System.IO (IOMode (..), withFile) import System.IO.Error (isPermissionError)+import System.Landlock+  ( AccessFsFlag (..),+    OpenPathFlags (..),+    RulesetAttr (..),+    abiVersion,+    accessFsFlags,+    defaultOpenPathFlags,+    isSupported,+    landlock,+    version1,+    version2,+    version3,+    withOpenPath,+  )+import System.Landlock.Flags+  ( CreateRulesetFlag,+    accessFsFlagToBit,+    createRulesetFlagToBit,+  )+import System.Landlock.Rules (Rule, RuleType (..), pathBeneath)+import System.Landlock.Syscalls (LandlockRulesetAttr (..))+import System.Landlock.Version (Version (..)) import System.Posix.Types (Fd)-import System.Process (CreateProcess(..), proc, readCreateProcessWithExitCode)--import Test.QuickCheck.Monadic (assert, monadicIO, run)+import System.Process (CreateProcess (..), proc, readCreateProcessWithExitCode)+import Test.QuickCheck ((=/=), (===))+import Test.QuickCheck.Classes.Base+  ( Laws (..),+    boundedEnumLaws,+    eqLaws,+    ordLaws,+    showLaws,+    storableLaws,+  ) import Test.Tasty (TestTree, defaultMain, testGroup)-import Test.Tasty.ExpectedFailure (expectFailBecause)-import Test.Tasty.HUnit ((@?), (@=?), (@?=), testCase, testCaseSteps)-import Test.Tasty.QuickCheck as QC--import System.Landlock (AccessFsFlag(..), RulesetAttr(..), OpenPathFlags(..), abiVersion, accessFsFlags, defaultOpenPathFlags, isSupported, landlock, version1, withOpenPath)-import System.Landlock.Rules (Rule, RuleType(..), pathBeneath)-import System.Landlock.Syscalls (LandlockRulesetAttr(..))-+import Test.Tasty.HUnit (assertBool, assertFailure, testCase, testCaseSteps, (@=?), (@?), (@?=))+import Test.Tasty.QuickCheck (Arbitrary (..), arbitraryBoundedEnum, testProperty) import ThreadedScenario (scenario)  -- This test-suite is a bit "weird". We want to test various privilege-related@@ -54,106 +73,210 @@ landlockTestEnvironmentVariable = "LANDLOCK_TEST"  main :: IO ()-main = lookupEnv "LANDLOCK_TEST" >>= \case+main =+  lookupEnv "LANDLOCK_TEST" >>= \case     Nothing -> do-        hasLandlock <- isSupported-        defaultMain (tests hasLandlock)+      defaultMain tests     Just testName -> case lookup testName functionalTestCases of-        Nothing -> fail $ "Unknown test: " ++ testName-        Just act -> act+      Nothing -> fail $ "Unknown test: " ++ testName+      Just act -> act -tests :: Bool -> TestTree-tests hasLandlock = testGroup "Tests" [-      properties-    , (if hasLandlock then id else expectFailBecause "Landlock not supported") functionalTests-    , (if hasLandlock then id else expectFailBecause "Landlock not supported") (scenario withAsync)+tests :: TestTree+tests =+  testGroup+    "Tests"+    [ properties,+      unitTests,+      functionalTests,+      scenario withAsync     ]  properties :: TestTree-properties = testGroup "Properties" [-      storable "LandlockRulesetAttr" (Proxy @LandlockRulesetAttr)-    , storable "Rule 'PathBeneath" (Proxy @(Rule 'PathBeneath))+properties =+  testGroup+    "Properties"+    [ testGroup "LandlockRulesetAttr" $+        map+          lawsToTestTree+          [ eqLaws (Proxy @LandlockRulesetAttr),+            showLaws (Proxy @LandlockRulesetAttr),+            storableLaws (Proxy @LandlockRulesetAttr)+          ],+      testGroup "RulesetAttr" $+        map+          lawsToTestTree+          [ eqLaws (Proxy @RulesetAttr),+            showLaws (Proxy @RulesetAttr)+          ],+      testGroup "CreateRulesetFlag" $+        map+          lawsToTestTree+          [ eqLaws (Proxy @CreateRulesetFlag),+            showLaws (Proxy @CreateRulesetFlag)+            -- boundedEnum laws don't work nicely with single-constructor enums+            -- , boundedEnumLaws (Proxy @CreateRulesetFlag)+          ],+      testGroup "AccessFsFlag" $+        map+          lawsToTestTree+          [ eqLaws (Proxy @AccessFsFlag),+            showLaws (Proxy @AccessFsFlag),+            boundedEnumLaws (Proxy @AccessFsFlag),+            ordLaws (Proxy @AccessFsFlag)+          ],+      testGroup "OpenPathFlags" $+        map+          lawsToTestTree+          [ eqLaws (Proxy @OpenPathFlags),+            showLaws (Proxy @OpenPathFlags)+          ],+      testGroup "Rule 'PathBeneath" $+        map+          lawsToTestTree+          [ eqLaws (Proxy @(Rule 'PathBeneath)),+            showLaws (Proxy @(Rule 'PathBeneath)),+            storableLaws (Proxy @(Rule 'PathBeneath))+          ],+      testGroup "Version" $+        map+          lawsToTestTree+          [ eqLaws (Proxy @Version),+            showLaws (Proxy @Version),+            ordLaws (Proxy @Version)+          ],+      testProperty "accessFsFlagToBit is different for different flags" $+        mapEq accessFsFlagToBit,+      testProperty "createRulesetFlagToBit is different for different flags" $+        mapEq createRulesetFlagToBit     ]+  where+    lawsToTestTree laws = testGroup (lawsTypeclass laws) $ flip map (lawsProperties laws) $ uncurry testProperty+    mapEq fn a b =+      let cmp = if a == b then (===) else (=/=)+       in fn a `cmp` fn b -storable :: forall proxy a. (Eq a, Show a, Arbitrary a, Storable a) => String -> proxy a -> TestTree-storable name _ = testGroup ("Storable for " ++ name) [-      QC.testProperty "peek . poke == id" $-          \a -> monadicIO $ do-              a' <- run $ alloca $ \ptr -> do-                  poke ptr (a :: a)-                  peek ptr-              assert $ a' == a-    ]+instance Arbitrary Version where+  arbitrary = Version <$> arbitrary +instance Arbitrary OpenPathFlags where+  arbitrary = OpenPathFlags <$> arbitrary <*> arbitrary <*> arbitrary++instance Arbitrary RulesetAttr where+  arbitrary = RulesetAttr <$> arbitrary++instance Arbitrary CreateRulesetFlag where+  arbitrary = arbitraryBoundedEnum+ instance Arbitrary LandlockRulesetAttr where-    arbitrary = LandlockRulesetAttr <$> arbitrary+  arbitrary = LandlockRulesetAttr <$> arbitrary  instance Arbitrary (Rule 'PathBeneath) where-    arbitrary = pathBeneath <$> fmap (fromIntegral :: Int -> Fd) arbitrary-                            <*> fmap (nub . sort) arbitrary+  arbitrary =+    pathBeneath+      <$> fmap (fromIntegral :: Int -> Fd) arbitrary+      <*> fmap (nub . sort) arbitrary  instance Arbitrary AccessFsFlag where-    arbitrary = arbitraryBoundedEnum+  arbitrary = arbitraryBoundedEnum +unitTests :: TestTree+unitTests =+  testGroup+    "Unit Tests"+    [ testCase "lookup version1 accessFsFlags" $+        lookup version1 accessFsFlags @?= Just [AccessFsExecute .. AccessFsMakeSym],+      testCase "lookup version2 accessFsFlags" $+        lookup version2 accessFsFlags @?= Just [AccessFsExecute .. AccessFsRefer],+      testCase "lookup version3 accessFsFlags" $+        lookup version3 accessFsFlags @?= Just [AccessFsExecute .. AccessFsTruncate],+      testCase "ABI v2 introduced [AccessFsRefer]" $+        (\\)+          <$> lookup version2 accessFsFlags+          <*> lookup version1 accessFsFlags+          @?= Just [AccessFsRefer],+      testCase "ABI v3 introduced [AccessFsTruncate]" $+        (\\)+          <$> lookup version3 accessFsFlags+          <*> lookup version2 accessFsFlags+          @?= Just [AccessFsTruncate],+      testCase "accessFsFlagToBit is unique for all AccessFsFlags" $+        length (nub (sort (map accessFsFlagToBit [minBound .. maxBound])))+          @?= length [minBound :: AccessFsFlag .. maxBound],+      testCase "createRulesetFlagToBit is unique for all CreateRulesetFlags" $+        length (nub (sort (map createRulesetFlagToBit [minBound .. maxBound])))+          @?= length [minBound :: CreateRulesetFlag .. maxBound]+    ]  functionalTests :: TestTree-functionalTests = testGroup "Functional Tests" $ [-      testCase "abiVersion >= 1" $ do-          v <- abiVersion-          v >= version1 @? "Unexpected version"-    , testCase "abiVersion is idempotent" $ do-          v1 <- abiVersion-          v2 <- abiVersion-          v1 @=? v2-    ] ++ map (\(name, _) -> testCaseSteps name (`runFunctionalTest` name)) functionalTestCases+functionalTests =+  testGroup "Functional Tests" $+    [ testCase "abiVersion >= 1" $ do+        v <- abiVersion+        v >= version1 @? "Unexpected version",+      testCase "abiVersion is idempotent" $ do+        v1 <- abiVersion+        v2 <- abiVersion+        v1 @=? v2,+      testCase "isSupported" $+        isSupported >>= assertBool "landlock API not supported"+    ]+      ++ map (\(name, _) -> testCaseSteps name (`runFunctionalTest` name)) functionalTestCases   where     runFunctionalTest step name = do-        step "Running test subprocess"-        (rc, stdout, stderr) <- readCreateProcessWithExitCode (mkCreateProcess name) ""-        step $ "Test subprocess exited with " ++ show rc-        unless (null stdout) $-            step $ "Test subprocess stdout:\n" ++ stdout-        unless (null stderr) $-            step $ "Test subprocess stderr:\n" ++ stderr-        rc @?= ExitSuccess+      step "Running test subprocess"+      (rc, stdout, stderr) <- readCreateProcessWithExitCode (mkCreateProcess name) ""+      step $ "Test subprocess exited with " ++ show rc+      unless (null stdout) $+        step $+          "Test subprocess stdout:\n" ++ stdout+      unless (null stderr) $+        step $+          "Test subprocess stderr:\n" ++ stderr+      rc @?= ExitSuccess -    mkCreateProcess name = (proc "/proc/self/exe" []) { env = Just [(landlockTestEnvironmentVariable, name)]-                                                      , close_fds = True-                                                      }+    mkCreateProcess name =+      (proc "/proc/self/exe" [])+        { env = Just [(landlockTestEnvironmentVariable, name)],+          close_fds = True+        }  functionalTestCases :: [(String, IO ())]-functionalTestCases = [-      ("All v1 restrictions in sandbox", testAllV1Restrictions)-    , ("Restrict read, except for /etc", testRestrictReadExceptEtc)-    ]+functionalTestCases =+  [ ("All v1 restrictions in sandbox", testAllV1Restrictions),+    ("Restrict read, except for /etc", testRestrictReadExceptEtc)+  ]  testAllV1Restrictions :: IO () testAllV1Restrictions = do-    let fn = "/etc/resolv.conf"-        try act = withFile fn ReadMode act+  let fn = "/etc/resolv.conf"+      try act = withFile fn ReadMode act -    -- First, try to open as-is-    try (\_ -> return ())+  -- First, try to open as-is+  try (\_ -> return ()) -    -- Then, sandbox and try again-    let Just v1Restrictions = lookup version1 accessFsFlags-    landlock (RulesetAttr v1Restrictions) [] [] $ \_ -> return ()-    catchPermissionDenied $ try $ \_ -> fail $ "Still able to open " ++ fn+  -- Then, sandbox and try again+  v1Restrictions <- case lookup version1 accessFsFlags of+    Nothing -> assertFailure $ "Unknown ABI version: " ++ show version1+    Just r -> return r+  landlock (RulesetAttr v1Restrictions) [] [] $ \_ -> return ()+  catchPermissionDenied $ try $ \_ -> fail $ "Still able to open " ++ fn  testRestrictReadExceptEtc :: IO () testRestrictReadExceptEtc = do-    let dir = "/etc"-        file = dir </> "passwd"-        act = withFile file ReadMode $ \_ -> return ()-        Just v1Restrictions = lookup version1 accessFsFlags+  let dir = "/etc"+      file = dir </> "passwd"+      act = withFile file ReadMode $ \_ -> return ()+  v1Restrictions <- case lookup version1 accessFsFlags of+    Nothing -> assertFailure $ "Unknown ABI version: " ++ show version1+    Just r -> return r -    act+  act -    landlock (RulesetAttr v1Restrictions) [] [] $ \addRule -> do-        withOpenPath dir defaultOpenPathFlags { directory = True } $ \fd -> do-            addRule (pathBeneath fd [AccessFsReadFile, AccessFsReadDir, AccessFsExecute]) []+  landlock (RulesetAttr v1Restrictions) [] [] $ \addRule -> do+    withOpenPath dir defaultOpenPathFlags {directory = True} $ \fd -> do+      addRule (pathBeneath fd [AccessFsReadFile, AccessFsReadDir, AccessFsExecute]) [] -    act+  act  catchPermissionDenied :: IO () -> IO () catchPermissionDenied = handleJust (\exc -> if isPermissionError exc then Just () else Nothing) return
+ test/landlocked-test.hs view
@@ -0,0 +1,60 @@+module Main (main) where++import System.Exit (ExitCode (..))+import System.FilePath ((</>))+import System.IO.Temp (withSystemTempDirectory)+import System.Process (readProcessWithExitCode)+import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.HUnit (assertFailure, testCase, (@?=))++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests =+  testGroup+    "landlocked"+    [ testCase "landlocked" $ do+        rc <- landlocked []+        case rc of+          ExitSuccess -> assertFailure "Unexpected success"+          ExitFailure _ -> return (),+      testCase "landlocked true" $ do+        rc <- landlocked ["true"]+        rc @?= ExitFailure 126,+      testCase "landlocked nosuchexecutablereally" $ do+        rc <- landlocked ["nosuchexecutablereally"]+        rc @?= ExitFailure 127,+      testCase "landlocked --ro /usr /usr/bin/true" $ do+        rc <- landlocked ["--ro", "/usr", "/usr/bin/true"]+        rc @?= ExitSuccess,+      testCase "landlocked --ro /usr true" $ do+        rc <- landlocked ["--ro", "/usr", "true"]+        rc @?= ExitSuccess,+      testCase "landlocked --ro /usr /usr/bin/false" $ do+        rc <- landlocked ["--ro", "/usr", "/usr/bin/false"]+        rc @?= ExitFailure 1,+      testCase "landlocked --ro /usr /usr/bin/touch TEMP_PATH/test" $+        withSystemTempDirectory "landlocked-test" $ \tmp -> do+          rc <- landlocked ["--ro", "/usr", "/usr/bin/touch", tmp </> "test"]+          rc @?= ExitFailure 1,+      testCase "landlocked --ro /usr --rw TEMP_PATH /usr/bin/touch TEMP_PATH/test" $+        withSystemTempDirectory "landlocked-test" $ \tmp -> do+          rc <- landlocked ["--ro", "/usr", "--rw", tmp, "/usr/bin/touch", tmp </> "test"]+          rc @?= ExitSuccess,+      testCase "landlocked --ro /usr --ro TEMP_PATH /usr/bin/touch TEMP_PATH/test" $+        withSystemTempDirectory "landlocked-test" $ \tmp -> do+          rc <- landlocked ["--ro", "/usr", "--ro", tmp, "/usr/bin/touch", tmp </> "test"]+          rc @?= ExitFailure 1,+      testCase "landlocked --version" $ do+        rc <- landlocked ["--version"]+        rc @?= ExitSuccess,+      testCase "landlocked --help" $ do+        rc <- landlocked ["--help"]+        rc @?= ExitSuccess+    ]++landlocked :: [String] -> IO ExitCode+landlocked args = do+  (rc, _, _) <- readProcessWithExitCode "landlocked" args ""+  return rc