[
  {
    "path": ".github/workflows/pages.yml",
    "content": "name: GitHub Pages\n\non:\n  push:\n    branches: [ main ]\n\n  workflow_dispatch:\n\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: false\n\njobs:\n  build:\n    if: github.repository == 'landlock-lsm/rust-landlock'\n    runs-on: ubuntu-24.04\n\n    env:\n      CARGO_TERM_COLOR: always\n\n    permissions:\n      contents: read\n      id-token: write\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Install Rust stable\n      run: |\n        rm ~/.cargo/bin/{cargo-fmt,rustfmt} || :\n        rustup default stable\n        rustup update\n\n    - name: Build documentation\n      run: rustup run stable cargo doc --no-deps\n\n    - name: Add index\n      run: |\n        echo '<meta http-equiv=\"refresh\" content=\"0; url=landlock\">' > target/doc/index.html\n\n    - name: Upload artifact\n      uses: actions/upload-pages-artifact@v3\n      with:\n        path: target/doc\n\n  deploy:\n    needs: build\n    runs-on: ubuntu-24.04\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n\n    permissions:\n      pages: write\n      id-token: write\n\n    steps:\n      - name: Deploy to GitHub Pages\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Rust checks\n\npermissions: {}\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\nenv:\n  CARGO_TERM_COLOR: always\n  RUSTDOCFLAGS: -D warnings\n  RUSTFLAGS: -D warnings\n  LANDLOCK_TEST_TOOLS_COMMIT: fad769c39b42183fb2a2e1263fe00dfa5b9f2bda\n\n# Ubuntu versions: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources\n\njobs:\n  commit_list:\n    runs-on: ubuntu-24.04\n    steps:\n\n    - uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n\n    - name: Get commit list (push)\n      id: get_commit_list_push\n      if: ${{ github.event_name == 'push' }}\n      run: |\n        echo \"id0=$GITHUB_SHA\" > $GITHUB_OUTPUT\n        echo \"List of tested commits:\" > $GITHUB_STEP_SUMMARY\n        sed -n 's,^id[0-9]\\+=\\(.*\\),- https://github.com/landlock-lsm/rust-landlock/commit/\\1,p' -- $GITHUB_OUTPUT >> $GITHUB_STEP_SUMMARY\n\n    - name: Get commit list (PR)\n      id: get_commit_list_pr\n      if: ${{ github.event_name == 'pull_request' }}\n      run: |\n        git rev-list --reverse refs/remotes/origin/${{ github.base_ref }}..${{ github.event.pull_request.head.sha }} | awk '{ print \"id\" NR \"=\" $1 }' > $GITHUB_OUTPUT\n        git diff --quiet ${{ github.event.pull_request.head.sha }} ${{ github.sha }} || echo \"id0=$GITHUB_SHA\" >> $GITHUB_OUTPUT\n        echo \"List of tested commits:\" > $GITHUB_STEP_SUMMARY\n        sed -n 's,^id[0-9]\\+=\\(.*\\),- https://github.com/landlock-lsm/rust-landlock/commit/\\1,p' -- $GITHUB_OUTPUT >> $GITHUB_STEP_SUMMARY\n\n    outputs:\n      commits: ${{ toJSON(steps.*.outputs.*) }}\n\n  kernel_list:\n    runs-on: ubuntu-24.04\n    outputs:\n      kernels: ${{ toJSON(steps.id.outputs.*) }}\n    steps:\n\n    - name: Identify latest Linux versions\n      id: id\n      run: |\n        echo \"List of tested kernels:\" > $GITHUB_STEP_SUMMARY\n        abi=0\n        for version in 5.10 5.15 6.1 6.4 6.7 6.10 6.12; do\n          commit=\"$(git ls-remote https://github.com/landlock-lsm/linux refs/heads/linux-${version}.y | awk 'NR == 1 { print $1 }')\"\n          if [[ -z \"${commit}\" ]]; then\n            echo \"ERROR: Failed to fetch Linux ${version}\" >&2\n            exit 1\n          fi\n          echo \"kernel_${abi}={ \\\"version\\\":\\\"${version}\\\",  \\\"abi\\\":\\\"${abi}\\\",  \\\"commit\\\":\\\"${commit}\\\" }\" >> $GITHUB_OUTPUT\n          echo \"- Linux ${version}.y with Landlock ABI ${abi}: https://github.com/landlock-lsm/linux/commit/${commit}\" >> $GITHUB_STEP_SUMMARY\n          let abi++ || :\n        done\n\n  get_kernels:\n    runs-on: ubuntu-24.04\n    needs: kernel_list\n    strategy:\n      fail-fast: false\n      matrix:\n        kernel: ${{ fromJSON(needs.kernel_list.outputs.kernels) }}\n    steps:\n\n    - name: Cache Linux ${{ fromJSON(matrix.kernel).version}}.y\n      id: cache_linux\n      uses: actions/cache@v4\n      with:\n        path: linux-${{ fromJSON(matrix.kernel).version }}\n        key: linux-${{ fromJSON(matrix.kernel).commit }}-${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}\n\n    - name: Clone Landlock test tools\n      if: steps.cache_linux.outputs.cache-hit != 'true'\n      uses: actions/checkout@v4\n      with:\n        repository: landlock-lsm/landlock-test-tools\n        ref: ${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}\n        path: landlock-test-tools\n\n    - name: Clone Linux ${{ fromJSON(matrix.kernel).version}}.y\n      if: steps.cache_linux.outputs.cache-hit != 'true'\n      uses: actions/checkout@v4\n      with:\n        repository: landlock-lsm/linux\n        ref: ${{ fromJSON(matrix.kernel).commit }}\n        path: linux\n\n    - name: Build Linux ${{ fromJSON(matrix.kernel).version}}.y\n      if: steps.cache_linux.outputs.cache-hit != 'true'\n      working-directory: linux\n      run: |\n        O=. ../landlock-test-tools/check-linux.sh build_light\n        mv linux ../linux-${{ fromJSON(matrix.kernel).version}}\n\n  ubuntu_24_rust_msrv:\n    runs-on: ubuntu-24.04\n    needs: commit_list\n    strategy:\n      fail-fast: false\n      matrix:\n        commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}\n    steps:\n\n    - uses: actions/checkout@v4\n      with:\n        ref: ${{ matrix.commit }}\n\n    - name: Clone Landlock test tools\n      uses: actions/checkout@v4\n      with:\n        repository: landlock-lsm/landlock-test-tools\n        ref: ${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}\n        path: landlock-test-tools\n\n    - name: Get MSRV\n      run: sed -n 's/^rust-version = \"\\([0-9.]\\+\\)\"$/RUST_TOOLCHAIN=\\1/p' Cargo.toml >> $GITHUB_ENV\n\n    - name: Install 32-bit development libraries\n      run: |\n        sudo apt update\n        sudo apt install gcc-multilib libc6-dev-i386\n\n    - name: Install Rust MSRV\n      run: |\n        rm ~/.cargo/bin/{cargo-fmt,rustfmt} || :\n        rustup self update\n        rustup default ${{ env.RUST_TOOLCHAIN }}\n        rustup update ${{ env.RUST_TOOLCHAIN }}\n        rustup target add i686-unknown-linux-gnu\n        rustup target add x86_64-unknown-linux-gnu\n\n    - name: Build (x86_64)\n      run: rustup run ${{ env.RUST_TOOLCHAIN }} cargo build --target x86_64-unknown-linux-gnu --verbose\n\n    - name: Build (x86)\n      run: rustup run ${{ env.RUST_TOOLCHAIN }} cargo build --target i686-unknown-linux-gnu --verbose\n\n    - name: Run tests against the local kernel (Landlock ABI ${{ env.LANDLOCK_CRATE_TEST_ABI }} on x86_64)\n      run: rustup run ${{ env.RUST_TOOLCHAIN }} cargo test --target x86_64-unknown-linux-gnu --verbose\n\n    - name: Run tests against the local kernel (Landlock ABI ${{ env.LANDLOCK_CRATE_TEST_ABI }} on x86)\n      run: rustup run ${{ env.RUST_TOOLCHAIN }} cargo test --target i686-unknown-linux-gnu --verbose\n\n    - name: Run tests against Linux 6.1\n      run: CARGO=\"rustup run ${{ env.RUST_TOOLCHAIN }} cargo\" ./landlock-test-tools/test-rust.sh linux-6.1 2\n\n  ubuntu_22_rust_stable:\n    runs-on: ubuntu-22.04\n    needs: commit_list\n    strategy:\n      fail-fast: false\n      matrix:\n        commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}\n    env:\n      LANDLOCK_CRATE_TEST_ABI: 4\n    steps:\n\n    - name: Install Rust stable\n      run: |\n        rm ~/.cargo/bin/{cargo-fmt,rustfmt} || :\n        rustup self update\n        rustup default stable\n        rustup component add rustfmt clippy\n        rustup update\n\n    - uses: actions/checkout@v4\n      with:\n        ref: ${{ matrix.commit }}\n\n    - name: Build\n      run: rustup run stable cargo build --verbose\n\n    - name: Run tests against the local kernel (Landlock ABI ${{ env.LANDLOCK_CRATE_TEST_ABI }})\n      run: rustup run stable cargo test --verbose\n\n    - name: Check format\n      run: rustup run stable cargo fmt --all -- --check\n\n    - name: Check source with Clippy\n      run: rustup run stable cargo clippy -- --deny warnings\n\n    - name: Check tests with Clippy\n      run: rustup run stable cargo clippy --tests -- --deny warnings\n\n    - name: Check documentation\n      run: rustup run stable cargo doc --no-deps\n\n  ubuntu_24_rust_stable:\n    runs-on: ubuntu-24.04\n    needs: [commit_list, kernel_list, get_kernels]\n    strategy:\n      fail-fast: false\n      matrix:\n        commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}\n        kernel: ${{ fromJSON(needs.kernel_list.outputs.kernels) }}\n    env:\n      LANDLOCK_CRATE_TEST_ABI: 4\n      # $CARGO is used by landlock-test-tools/test-rust.sh\n      CARGO: rustup run stable cargo\n    steps:\n\n    - name: Install Rust stable\n      run: |\n        rm ~/.cargo/bin/{cargo-fmt,rustfmt} || :\n        rustup self update\n        rustup default stable\n        rustup update\n\n    - name: Clone Landlock test tools\n      uses: actions/checkout@v4\n      with:\n        repository: landlock-lsm/landlock-test-tools\n        ref: ${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}\n        path: landlock-test-tools\n\n    - name: Clone rust-landlock\n      uses: actions/checkout@v4\n      with:\n        ref: ${{ matrix.commit }}\n        path: rust-landlock\n\n    - name: Get Linux ${{ fromJSON(matrix.kernel).version}}.y\n      uses: actions/cache/restore@v4\n      with:\n        path: linux-${{ fromJSON(matrix.kernel).version }}\n        key: linux-${{ fromJSON(matrix.kernel).commit }}-${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}\n        fail-on-cache-miss: true\n\n    - name: Run tests against Linux ${{ fromJSON(matrix.kernel).version }}.y\n      working-directory: rust-landlock\n      run: ../landlock-test-tools/test-rust.sh ../linux-${{ fromJSON(matrix.kernel).version }} ${{ fromJSON(matrix.kernel).abi }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/Cargo.lock\n/target\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Landlock changelog\n\n## [v0.4.4](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.4)\n\n### New API\n\n- Added support for all architectures ([PR #111](https://github.com/landlock-lsm/rust-landlock/pull/111)).\n- Added `LandlockStatus` type to query the running kernel and display information about available Landlock features ([PR #103](https://github.com/landlock-lsm/rust-landlock/pull/103) and [PR #113](https://github.com/landlock-lsm/rust-landlock/pull/113)).\n\n### Dependencies\n\n- Bumped MSRV to Rust 1.68 ([PR #112](https://github.com/landlock-lsm/rust-landlock/pull/112)).\n\n### Testing\n\n- Extended CI to build and test on i686 architecture ([PR #111](https://github.com/landlock-lsm/rust-landlock/pull/111)).\n\n### Example\n\n- Enhanced sandboxer example to print helpful hints about Landlock status ([PR #103](https://github.com/landlock-lsm/rust-landlock/pull/103)).\n\n## [v0.4.3](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.3)\n\n### New API\n\n- Implemented common traits (e.g., `Debug`) for public types ([PR #108](https://github.com/landlock-lsm/rust-landlock/pull/108)).\n\n### Documentation\n\n- Extended [CONTRIBUTING.md](CONTRIBUTING.md) documentation with additional testing and development guidelines ([PR #95](https://github.com/landlock-lsm/rust-landlock/pull/95)).\n- Added more background information to [`path_beneath_rules()`](https://landlock.io/rust-landlock/landlock/fn.path_beneath_rules.html) documentation ([PR #94](https://github.com/landlock-lsm/rust-landlock/pull/94)).\n\n### Testing\n\n- Added test case for `AccessFs::from_file()` method ([PR #92](https://github.com/landlock-lsm/rust-landlock/pull/92)).\n\n## [v0.4.2](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.2)\n\n### New API\n\n- Added support for Landlock ABI 6: control abstract UNIX sockets and signal scoping with the new [`Ruleset::scope()`](https://landlock.io/rust-landlock/landlock/struct.Ruleset.html#method.scope) method taking a [`Scope`](https://landlock.io/rust-landlock/landlock/enum.Scope.html) enum ([PR #96](https://github.com/landlock-lsm/rust-landlock/pull/96) and [PR #98](https://github.com/landlock-lsm/rust-landlock/pull/98)).\n- Added `From<RulesetCreated>` implementation for `Option<OwnedFd>` ([PR #104](https://github.com/landlock-lsm/rust-landlock/pull/104))\n- Introduced a new [`HandledAccess`](https://landlock.io/rust-landlock/landlock/trait.HandledAccess.html) trait specific to `AccessFs` and `AccessNet` (commit [554217dda0b7](https://github.com/landlock-lsm/rust-landlock/commit/554217dda0b775756e38db71f471dd414b199234)).\n- Added a new [`Errno`](https://landlock.io/rust-landlock/landlock/struct.Errno.html) type to improve FFI support ([PR #86](https://github.com/landlock-lsm/rust-landlock/pull/86) and [PR #102](https://github.com/landlock-lsm/rust-landlock/pull/102)).\n- Exposed `From<i32>` implementation for [`ABI`](https://landlock.io/rust-landlock/landlock/enum.ABI.html) ([PR #88](https://github.com/landlock-lsm/rust-landlock/pull/88)).\n\n### Documentation\n\n- Added clarifying notes about `AccessFs::WriteFile` behavior and `path_beneath_rules` usage ([PR #80](https://github.com/landlock-lsm/rust-landlock/pull/80)).\n- Introduced [CONTRIBUTING.md](CONTRIBUTING.md) with testing workflow explanations ([PR #76](https://github.com/landlock-lsm/rust-landlock/pull/76)).\n\n### Testing\n\n- Enhanced test coverage for new API and added testing against Linux 6.12 ([PR #96](https://github.com/landlock-lsm/rust-landlock/pull/96)).\n- Updated CI configuration to use the latest Ubuntu versions ([PR #87](https://github.com/landlock-lsm/rust-landlock/pull/87) and [PR #97](https://github.com/landlock-lsm/rust-landlock/pull/97)).\n- Modified default `LANDLOCK_CRATE_TEST_ABI` to match the current kernel for more convenient local testing ([PR #76](https://github.com/landlock-lsm/rust-landlock/pull/76)).\n\n### Example\n\n- Synchronized the sandboxer example with the C version ([PR #101](https://github.com/landlock-lsm/rust-landlock/pull/101)): improved error handling for inaccessible file paths and enhanced help documentation.\n\n## [v0.4.1](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.1)\n\n### New API\n\nAdd support for Landlock ABI 5: control IOCTL commands on character and block devices with the new [`AccessFs::IoctlDev`](https://landlock.io/rust-landlock/landlock/enum.AccessFs.html#variant.IoctlDev) right ([PR #74](https://github.com/landlock-lsm/rust-landlock/pull/74)).\n\n### Testing\n\nImproved the CI to better test against different kernel versions ([PR #72](https://github.com/landlock-lsm/rust-landlock/pull/72)).\n\n\n## [v0.4.0](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.0)\n\n### New API\n\nAdd support for Landlock ABI 4: control TCP binding and connection according to specified network ports.\nThis is now possible with the [`AccessNet`](https://landlock.io/rust-landlock/landlock/enum.AccessNet.html) rights and\nthe [`NetPort`](https://landlock.io/rust-landlock/landlock/struct.NetPort.html) rule\n([PR #55](https://github.com/landlock-lsm/rust-landlock/pull/55)).\n\n### Breaking change\n\nThe `from_read()` and `from_write()` methods moved from the `Access` trait to the `AccessFs` struct\n([commit 68f066eba571](https://github.com/landlock-lsm/rust-landlock/commit/68f066eba571c1f9212f5a07016aac9ffb0d1c27)).\n\n### Compatibility management\n\nImprove compatibility consistency and prioritize runtime errors against compatibility errors\n([PR #67](https://github.com/landlock-lsm/rust-landlock/pull/67)).\n\nFixed a corner case where a ruleset was created on a kernel not supporting Landlock, while requesting to add a rule with an access right handled by the ruleset (`BestEffort`).\nWhen trying to enforce this ruleset, this led to a runtime error (i.e. wrong file descriptor) instead of a compatibility error.\n\nTo simplify compatibility management, always call `prctl(PR_SET_NO_NEW_PRIVS, 1)` by default (see `set_no_new_privs()`).\nThis was required to get a consistent compatibility management and it should not be an issue given that this feature is supported by all LTS kernels\n([commit d99f75155bec](https://github.com/landlock-lsm/rust-landlock/commit/d99f75155bec2040cf4ce1532007cd3b8a23e2fb)).\n\n\n## [v0.3.1](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.3.1)\n\n### New API\n\nAdd [`RulesetCreated::try_clone()`](https://landlock.io/rust-landlock/landlock/struct.RulesetCreated.html#method.try_clone) ([PR #38](https://github.com/landlock-lsm/rust-landlock/pull/38)).\n\n\n## [v0.3.0](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.3.0)\n\n### New API\n\nAdd support for Landlock ABI 3: control truncate operations with the new\n[`AccessFs::Truncate`](https://landlock.io/rust-landlock/landlock/enum.AccessFs.html#variant.Truncate)\nright ([PR #40](https://github.com/landlock-lsm/rust-landlock/pull/40)).\n\nRevamp the compatibility handling and add a new\n[`set_compatibility()`](https://landlock.io/rust-landlock/landlock/trait.Compatible.html#method.set_compatibility)\nmethod for `Ruleset`, `RulesetCreated`, and `PathBeneath`.\nWe can now fine-tune the compatibility behavior according to the running kernel\nand then the supported features thanks to three compatible levels:\nbest effort, soft requirement and hard requirement\n([PR #12](https://github.com/landlock-lsm/rust-landlock/pull/12)).\n\nAdd a new [`AccessFs::from_file()`](https://landlock.io/rust-landlock/landlock/enum.AccessFs.html#method.from_file)\nhelper ([commit 0b3238c6dd70](https://github.com/landlock-lsm/rust-landlock/commit/0b3238c6dd70)).\n\n### Deprecated API\n\nDeprecate the [`set_best_effort()`](https://landlock.io/rust-landlock/landlock/trait.Compatible.html#method.set_best_effort)\nmethod and replace it with `set_compatibility()`\n([PR #12](https://github.com/landlock-lsm/rust-landlock/pull/12)).\n\nDeprecate [`Ruleset::new()`](https://landlock.io/rust-landlock/landlock/struct.Ruleset.html#method.new)\nand replace it with `Ruleset::default()`\n([PR #44](https://github.com/landlock-lsm/rust-landlock/pull/44)).\n\n### Breaking changes\n\nWe now check that a ruleset really handles at least one access right,\nwhich can now cause `Ruleset::create()` to return an error if the ruleset compatibility level is\n`HardRequirement` or `set_best_effort(false)`\n([commit 95addc13b4a8](https://github.com/landlock-lsm/rust-landlock/commit/95addc13b4a8)).\n\nWe now check that access rights passed to `add_rule()` make sense according to the file type.\nTo handle most use cases,\n`path_beneath_rules()` now automatically check and downgrade access rights for files\n(i.e. remove superfluous directory-only access rights,\n [commit 8e47940b3722](https://github.com/landlock-lsm/rust-landlock/commit/8e47940b3722)).\n\n### Testing\n\nTest coverage in the CI is greatly improved by running all tests on all relevant kernel versions:\nLinux 5.10, 5.15, 6.1, and 6.4\n([PR #41](https://github.com/landlock-lsm/rust-landlock/pull/41)).\n\nRun each test in a dedicated thread to avoid inconsistent behavior\n([PR #46](https://github.com/landlock-lsm/rust-landlock/pull/46)).\n\n\n## [v0.2.0](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.2.0)\n\nThis is the first major release of this crate.\nIt brings a high-level interface to the Landlock kernel interface.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThanks for your interest in contributing to rust-landlock!\n\n## Testing vs kernel ABI\n\nThe Landlock functionality exposed differs between kernel versions. Based on\nthe Landlock ABI version of the running system, rust-landlock runs different\nsubsets of tests. For local development, running `cargo test` will test against\nyour currently running kernel version (and the Landlock ABI that it ships).\nHowever, this may result in some tests being skipped.\n\nTo fully test a change, it should be verified against a range of ABI versions.\nThis is done by the Github Actions CI, but doing so locally is challenging.\n\nUsing the `LANDLOCK_CRATE_TEST_ABI` variable, the tested ABI version can be\noverridden. For more details, take a look at the comment in\n[`compat.rs:current_kernel_abi()`][current-kernel-abi].\n\nFor more information about Landlock ABIs, see: [enum ABI][enum-abi]\n\n[current-kernel-abi]: https://github.com/landlock-lsm/rust-landlock/blob/main/src/compat.rs\n[enum-abi]: https://landlock.io/rust-landlock/landlock/enum.ABI.html\n\n## Licensing & DCO\n\nrust-landlock is double-licensed under the terms of [Apache 2.0][apache-2.0]\nand [MIT][mit].\n\nAll changes submitted to rust-landlock must be [signed off][dco].\n\n[apache-2.0]: https://spdx.org/licenses/Apache-2.0.html\n[mit]: https://spdx.org/licenses/MIT.html\n[dco]: https://github.com/apps/dco\n"
  },
  {
    "path": "COPYRIGHT",
    "content": "Copyright 2020 Mickaël Salaün\n\nLicensed under the Apache License, Version 2.0 <LICENSE-APACHE or\nhttps://www.apache.org/licenses/LICENSE-2.0> or the MIT license\n<LICENSE-MIT or https://opensource.org/licenses/MIT>, at your\noption. All files in the project carrying such notice may not be\ncopied, modified, or distributed except according to those terms.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"landlock\"\nversion = \"0.4.4\"\nedition = \"2021\"\nrust-version = \"1.71\"\ndescription = \"Landlock LSM helpers\"\nhomepage = \"https://landlock.io\"\nrepository = \"https://github.com/landlock-lsm/rust-landlock\"\nlicense = \"MIT OR Apache-2.0\"\nkeywords = [\"access-control\", \"linux\", \"sandbox\", \"security\"]\ncategories = [\"api-bindings\", \"os::linux-apis\", \"virtualization\", \"filesystem\"]\nexclude = [\".gitignore\"]\nreadme = \"README.md\"\n\n[dependencies]\nenumflags2 = \"0.7\"\nlibc = \"0.2.175\"\nthiserror = \"2.0\"\n\n[dev-dependencies]\nanyhow = \"1.0\"\nlazy_static = \"1\"\nstrum = \"0.26\"\nstrum_macros = \"0.26\"\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2020 Mickaël Salaün\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "MIT License\n\nCopyright (c) 2020 Mickaël Salaün\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Rust Landlock library\n\nLandlock is a security feature available since Linux 5.13.\nThe goal is to enable to restrict ambient rights (e.g., global filesystem access) for a set of processes by creating safe security sandboxes as new security layers in addition to the existing system-wide access-controls.\nThis kind of sandbox is expected to help mitigate the security impact of bugs, unexpected or malicious behaviors in applications.\nLandlock empowers any process, including unprivileged ones, to securely restrict themselves.\nMore information about Landlock can be found in the [official website](https://landlock.io).\n\nThis Rust crate provides a safe abstraction for the Landlock system calls along with some helpers.\n\n## Use cases\n\nThis crate is especially useful to protect users' data by sandboxing:\n* trusted applications dealing with potentially malicious data\n  (e.g., complex file format, network request) that could exploit security vulnerabilities;\n* sandbox managers, container runtimes or shells launching untrusted applications.\n\n## Examples\n\nA simple example can be found with the\n[`path_beneath_rules()`](https://landlock.io/rust-landlock/landlock/fn.path_beneath_rules.html) helper.\nMore complex examples can be found with the\n[`Ruleset` documentation](https://landlock.io/rust-landlock/landlock/struct.Ruleset.html)\nand the [sandboxer example](examples/sandboxer.rs).\n\n## [Crate documentation](https://landlock.io/rust-landlock/landlock/)\n\n## Changelog\n\n* [v0.4.4](CHANGELOG.md#v044)\n* [v0.4.3](CHANGELOG.md#v043)\n* [v0.4.2](CHANGELOG.md#v042)\n* [v0.4.1](CHANGELOG.md#v041)\n* [v0.4.0](CHANGELOG.md#v040)\n* [v0.3.1](CHANGELOG.md#v031)\n* [v0.3.0](CHANGELOG.md#v030)\n* [v0.2.0](CHANGELOG.md#v020)\n"
  },
  {
    "path": "examples/sandboxer.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// This is an idiomatic Rust rewrite of a C example:\n// https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/samples/landlock/sandboxer.c\n\nuse anyhow::{anyhow, bail, Context};\nuse landlock::{\n    path_beneath_rules, Access, AccessFs, AccessNet, BitFlags, LandlockStatus, NetPort,\n    PathBeneath, PathFd, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus, Scope, ABI,\n};\nuse std::env;\nuse std::ffi::OsStr;\nuse std::os::unix::ffi::{OsStrExt, OsStringExt};\nuse std::os::unix::process::CommandExt;\nuse std::process::Command;\n\nconst ENV_FS_RO_NAME: &str = \"LL_FS_RO\";\nconst ENV_FS_RW_NAME: &str = \"LL_FS_RW\";\nconst ENV_TCP_BIND_NAME: &str = \"LL_TCP_BIND\";\nconst ENV_TCP_CONNECT_NAME: &str = \"LL_TCP_CONNECT\";\nconst ENV_SCOPED_NAME: &str = \"LL_SCOPED\";\n\nstruct PathEnv {\n    paths: Vec<u8>,\n    access: BitFlags<AccessFs>,\n}\n\nimpl PathEnv {\n    /// Create an object able to iterate PathBeneath rules\n    ///\n    /// # Arguments\n    ///\n    /// * `name`: String identifying an environment variable containing paths requested to be\n    ///   allowed. Paths are separated with \":\", e.g. \"/bin:/lib:/usr:/proc\". In case an empty\n    ///   string is provided, NO restrictions are applied.\n    /// * `access`: Set of access-rights allowed for each of the parsed paths.\n    fn new<'a>(name: &'a str, access: BitFlags<AccessFs>) -> anyhow::Result<Self> {\n        Ok(Self {\n            paths: env::var_os(name)\n                .ok_or(anyhow!(\"missing environment variable {name}\"))?\n                .into_vec(),\n            access,\n        })\n    }\n\n    fn iter(&self) -> impl Iterator<Item = anyhow::Result<PathBeneath<PathFd>>> + '_ {\n        let is_empty = self.paths.is_empty();\n        path_beneath_rules(\n            self.paths\n                .split(|b| *b == b':')\n                // Skips the first empty element of an empty string.\n                .skip_while(move |_| is_empty)\n                .map(OsStr::from_bytes),\n            self.access,\n        )\n        .map(|r| Ok(r?))\n    }\n}\n\nstruct PortEnv {\n    ports: Vec<u8>,\n    access: AccessNet,\n}\n\nimpl PortEnv {\n    fn new<'a>(name: &'a str, access: AccessNet) -> anyhow::Result<Self> {\n        Ok(Self {\n            ports: env::var_os(name).unwrap_or_default().into_vec(),\n            access,\n        })\n    }\n\n    fn iter(&self) -> impl Iterator<Item = anyhow::Result<NetPort>> + '_ {\n        let is_empty = self.ports.is_empty();\n        self.ports\n            .split(|b| *b == b':')\n            // Skips the first empty element of an empty string.\n            .skip_while(move |_| is_empty)\n            .map(OsStr::from_bytes)\n            .map(|port| {\n                let port = port\n                    .to_str()\n                    .context(\"failed to convert port string\")?\n                    .parse::<u16>()\n                    .context(\"failed to convert port to 16-bit integer\")?;\n                Ok(NetPort::new(port, self.access))\n            })\n    }\n}\n\nfn main() -> anyhow::Result<()> {\n    let mut args = env::args_os();\n    let program_name = args\n        .next()\n        .context(\"Missing the sandboxer program name (i.e. argv[0])\")?;\n\n    let cmd_name = args.next().ok_or_else(|| {\n        let program_name = program_name.to_string_lossy();\n        eprintln!(\n            \"usage: {ENV_FS_RO_NAME}=\\\"...\\\" {ENV_FS_RW_NAME}=\\\"...\\\" [other environment variables] {program_name} <cmd> [args]...\\n\"\n        );\n        eprintln!(\"Execute the given command in a restricted environment.\");\n        eprintln!(\"Multi-valued settings (lists of ports, paths, scopes) are colon-delimited.\\n\");\n        eprintln!(\"Mandatory settings:\");\n        eprintln!(\"* {ENV_FS_RO_NAME}: paths allowed to be used in a read-only way\");\n        eprintln!(\"* {ENV_FS_RW_NAME}: paths allowed to be used in a read-write way\\n\");\n        eprintln!(\"Optional settings (when not set, their associated access check is always allowed, which is different from an empty string which means an empty list):\");\n        eprintln!(\"* {ENV_TCP_BIND_NAME}: ports allowed to bind (server)\");\n        eprintln!(\"* {ENV_TCP_CONNECT_NAME}: ports allowed to connect (client)\");\n        eprintln!(\"* {ENV_SCOPED_NAME}: actions denied on the outside of the Landlock domain:\");\n        eprintln!(\"  - \\\"a\\\" to restrict opening abstract unix sockets\");\n        eprintln!(\"  - \\\"s\\\" to restrict sending signals\");\n        eprintln!(\n            \"\\nExample:\\n\\\n                {ENV_FS_RO_NAME}=\\\"${{PATH}}:/lib:/usr:/proc:/etc:/dev/urandom\\\" \\\n                {ENV_FS_RW_NAME}=\\\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\\\" \\\n                {ENV_TCP_BIND_NAME}=\\\"9418\\\" \\\n                {ENV_TCP_CONNECT_NAME}=\\\"80:443\\\" \\\n                {ENV_SCOPED_NAME}=\\\"a:s\\\" \\\n                {program_name} bash -i\\n\"\n        );\n        anyhow!(\"Missing command\")\n    })?;\n\n    let abi = ABI::V6;\n    let mut ruleset = Ruleset::default().handle_access(AccessFs::from_all(abi))?;\n    let ruleset_ref = &mut ruleset;\n\n    if env::var_os(ENV_TCP_BIND_NAME).is_some() {\n        ruleset_ref.handle_access(AccessNet::BindTcp)?;\n    }\n    if env::var_os(ENV_TCP_CONNECT_NAME).is_some() {\n        ruleset_ref.handle_access(AccessNet::ConnectTcp)?;\n    }\n\n    if let Some(scoped) = env::var_os(ENV_SCOPED_NAME) {\n        let mut abstract_scoping = false;\n        let mut signal_scoping = false;\n        let scopes = scoped.to_string_lossy();\n        let is_empty = scopes.is_empty();\n        for scope in scopes.split(':').skip_while(move |_| is_empty) {\n            match scope {\n                \"a\" => {\n                    if abstract_scoping {\n                        bail!(\"Duplicate scope 'a'\");\n                    }\n                    ruleset_ref.scope(Scope::AbstractUnixSocket)?;\n                    abstract_scoping = true;\n                }\n                \"s\" => {\n                    if signal_scoping {\n                        bail!(\"Duplicate scope 's'\");\n                    }\n                    ruleset_ref.scope(Scope::Signal)?;\n                    signal_scoping = true;\n                }\n                _ => bail!(\"Unknown scope \\\"{scope}\\\"\"),\n            }\n        }\n    }\n\n    let status = ruleset\n        .create()?\n        .add_rules(PathEnv::new(ENV_FS_RO_NAME, AccessFs::from_read(abi))?.iter())?\n        .add_rules(PathEnv::new(ENV_FS_RW_NAME, AccessFs::from_all(abi))?.iter())?\n        .add_rules(PortEnv::new(ENV_TCP_BIND_NAME, AccessNet::BindTcp)?.iter())?\n        .add_rules(PortEnv::new(ENV_TCP_CONNECT_NAME, AccessNet::ConnectTcp)?.iter())?\n        .restrict_self()\n        .expect(\"Failed to enforce ruleset\");\n\n    match status.landlock {\n        // This should never happen because of the previous check:\n        LandlockStatus::NotEnabled => {\n            eprintln!(\n                \"Hint: Landlock is currently disabled. \\\n                It can be enabled in the kernel configuration by prepending \\\"landlock,\\\"\n                to the content of CONFIG_LSM, or at boot time by setting the same content to\n                the \\\"lsm\\\" kernel parameter.\"\n            );\n        }\n        LandlockStatus::NotImplemented => {\n            eprintln!(\n                \"Hint: Landlock is not built into the current kernel. \\\n                To support it, build the kernel with CONFIG_SECURITY_LANDLOCK=y and \\\n                prepend \\\"landlock,\\\" to the content of CONFIG_LSM.\"\n            );\n        }\n        LandlockStatus::Available {\n            kernel_abi: Some(raw_abi),\n            ..\n        } => {\n            eprintln!(\n                \"Hint: This sandboxer only supports Landlock ABI version up to {abi} \\\n                whereas the current kernel supports Landlock ABI version {raw_abi}. \\\n                To leverage all Landlock features, update this sandboxer.\"\n            );\n        }\n        LandlockStatus::Available {\n            kernel_abi: None,\n            effective_abi,\n        } => {\n            if effective_abi < abi {\n                eprintln!(\n                    \"Hint: This sandboxer supports Landlock ABI version up to {abi} \\\n                    but the current kernel only supports Landlock ABI version {effective_abi}. \\\n                    To leverage all Landlock features, update the kernel.\"\n                );\n            } else if effective_abi > abi {\n                // This should not happen because the ABI used by the sandboxer\n                // should be the latest supported by the Landlock crate, and\n                // they should be updated at the same time.\n                eprintln!(\n                    \"Warning: This sandboxer only supports Landlock ABI version up to {abi} \\\n                    but the current kernel supports Landlock ABI version {effective_abi}. \\\n                    To leverage all Landlock features, update this sandboxer.\"\n                );\n            }\n        }\n    }\n    if status.ruleset == RulesetStatus::NotEnforced {\n        bail!(\"The ruleset cannot be enforced at all\");\n    }\n\n    eprintln!(\"Executing the sandboxed command...\");\n    Err(Command::new(cmd_name)\n        .env_remove(ENV_FS_RO_NAME)\n        .env_remove(ENV_FS_RW_NAME)\n        .env_remove(ENV_TCP_BIND_NAME)\n        .env_remove(ENV_TCP_CONNECT_NAME)\n        .args(args)\n        .exec()\n        .into())\n}\n"
  },
  {
    "path": "src/access.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::{\n    private, AccessError, AddRuleError, AddRulesError, BitFlags, CompatError, CompatResult,\n    HandleAccessError, HandleAccessesError, Ruleset, TailoredCompatLevel, TryCompat, ABI,\n};\nuse enumflags2::BitFlag;\n\n#[cfg(test)]\nuse crate::{make_bitflags, AccessFs, CompatLevel, CompatState, Compatibility};\n\npub trait Access: BitFlag + private::Sealed {\n    /// Gets the access rights defined by a specific [`ABI`].\n    fn from_all(abi: ABI) -> BitFlags<Self>;\n}\n\n// This HandledAccess trait is useful to document the API.\npub trait HandledAccess: Access {}\n\npub trait PrivateHandledAccess: HandledAccess {\n    fn ruleset_handle_access(\n        ruleset: &mut Ruleset,\n        access: BitFlags<Self>,\n    ) -> Result<(), HandleAccessesError>\n    where\n        Self: Access;\n\n    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError\n    where\n        Self: Access;\n\n    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError\n    where\n        Self: Access;\n}\n\n// Creates an illegal/overflowed BitFlags<T> with all its bits toggled, including undefined ones.\nfn full_negation<T>(flags: BitFlags<T>) -> BitFlags<T>\nwhere\n    T: Access,\n{\n    unsafe { BitFlags::<T>::from_bits_unchecked(!flags.bits()) }\n}\n\n#[test]\nfn bit_flags_full_negation() {\n    let scoped_negation = !BitFlags::<AccessFs>::all();\n    assert_eq!(scoped_negation, BitFlags::<AccessFs>::empty());\n    // !BitFlags::<AccessFs>::all() could be equal to full_negation(BitFlags::<AccessFs>::all()))\n    // if all the 64-bits would be used, which is not currently the case.\n    assert_ne!(scoped_negation, full_negation(BitFlags::<AccessFs>::all()));\n}\n\nimpl<A> TailoredCompatLevel for BitFlags<A> where A: Access {}\n\nimpl<A> TryCompat<A> for BitFlags<A>\nwhere\n    A: Access,\n{\n    fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>> {\n        if self.is_empty() {\n            // Empty access-rights would result to a runtime error.\n            Err(AccessError::Empty.into())\n        } else if !Self::all().contains(*self) {\n            // Unknown access-rights (at build time) would result to a runtime error.\n            // This can only be reached by using the unsafe BitFlags::from_bits_unchecked().\n            Err(AccessError::Unknown {\n                access: *self,\n                unknown: *self & full_negation(Self::all()),\n            }\n            .into())\n        } else {\n            let compat = *self & A::from_all(abi);\n            let ret = if compat.is_empty() {\n                Ok(CompatResult::No(\n                    AccessError::Incompatible { access: *self }.into(),\n                ))\n            } else if compat != *self {\n                let error = AccessError::PartiallyCompatible {\n                    access: *self,\n                    incompatible: *self & full_negation(compat),\n                }\n                .into();\n                Ok(CompatResult::Partial(error))\n            } else {\n                Ok(CompatResult::Full)\n            };\n            *self = compat;\n            ret\n        }\n    }\n}\n\n#[test]\nfn compat_bit_flags() {\n    use crate::ABI;\n\n    let mut compat: Compatibility = ABI::V1.into();\n    assert!(compat.state == CompatState::Init);\n\n    let ro_access = make_bitflags!(AccessFs::{Execute | ReadFile | ReadDir});\n    assert_eq!(\n        ro_access,\n        ro_access\n            .try_compat(compat.abi(), compat.level, &mut compat.state)\n            .unwrap()\n            .unwrap()\n    );\n    assert!(compat.state == CompatState::Full);\n\n    let empty_access = BitFlags::<AccessFs>::empty();\n    assert!(matches!(\n        empty_access\n            .try_compat(compat.abi(), compat.level, &mut compat.state)\n            .unwrap_err(),\n        CompatError::Access(AccessError::Empty)\n    ));\n\n    let all_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked(1 << 63) };\n    assert!(matches!(\n        all_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),\n        CompatError::Access(AccessError::Unknown { access, unknown }) if access == all_unknown_access && unknown == all_unknown_access\n    ));\n    // An error makes the state final.\n    assert!(compat.state == CompatState::Dummy);\n\n    let some_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked(1 << 63 | 1) };\n    assert!(matches!(\n        some_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),\n        CompatError::Access(AccessError::Unknown { access, unknown }) if access == some_unknown_access && unknown == all_unknown_access\n    ));\n    assert!(compat.state == CompatState::Dummy);\n\n    compat = ABI::Unsupported.into();\n\n    // Tests that the ruleset is marked as unsupported.\n    assert!(compat.state == CompatState::Init);\n\n    // Access-rights are valid (but ignored) when they are not required for the current ABI.\n    assert_eq!(\n        None,\n        ro_access\n            .try_compat(compat.abi(), compat.level, &mut compat.state)\n            .unwrap()\n    );\n\n    assert!(compat.state == CompatState::No);\n\n    // Access-rights are not valid when they are required for the current ABI.\n    compat.level = Some(CompatLevel::HardRequirement);\n    assert!(matches!(\n        ro_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),\n        CompatError::Access(AccessError::Incompatible { access }) if access == ro_access\n    ));\n\n    compat = ABI::V1.into();\n\n    // Tests that the ruleset is marked as the unknown compatibility state.\n    assert!(compat.state == CompatState::Init);\n\n    // Access-rights are valid (but ignored) when they are not required for the current ABI.\n    assert_eq!(\n        ro_access,\n        ro_access\n            .try_compat(compat.abi(), compat.level, &mut compat.state)\n            .unwrap()\n            .unwrap()\n    );\n\n    // Tests that the ruleset is in an unsupported state, which is important to be able to still\n    // enforce no_new_privs.\n    assert!(compat.state == CompatState::Full);\n\n    let v2_access = ro_access | AccessFs::Refer;\n\n    // Access-rights are not valid when they are required for the current ABI.\n    compat.level = Some(CompatLevel::HardRequirement);\n    assert!(matches!(\n        v2_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),\n        CompatError::Access(AccessError::PartiallyCompatible { access, incompatible })\n            if access == v2_access && incompatible == AccessFs::Refer\n    ));\n}\n"
  },
  {
    "path": "src/compat.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::{uapi, Access, CompatError};\nuse std::fmt::{self, Display, Formatter};\nuse std::io::Error;\n\n#[cfg(test)]\nuse std::convert::TryInto;\n#[cfg(test)]\nuse strum::{EnumCount, IntoEnumIterator};\n#[cfg(test)]\nuse strum_macros::{EnumCount as EnumCountMacro, EnumIter};\n\n/// Version of the Landlock [ABI](https://en.wikipedia.org/wiki/Application_binary_interface).\n///\n/// `ABI` enables getting the features supported by a specific Landlock ABI\n/// (without relying on the kernel version which may not be accessible or patched).\n/// For example, [`AccessFs::from_all(ABI::V1)`](Access::from_all)\n/// gets all the file system access rights defined by the first version.\n///\n/// Without `ABI`, it would be hazardous to rely on the the full set of access flags\n/// (e.g., `BitFlags::<AccessFs>::all()` or `BitFlags::ALL`),\n/// a moving target that would change the semantics of your Landlock rule\n/// when migrating to a newer version of this crate.\n/// Indeed, a simple `cargo update` or `cargo install` run by any developer\n/// can result in a new version of this crate (fixing bugs or bringing non-breaking changes).\n/// This crate cannot give any guarantee concerning the new restrictions resulting from\n/// these unknown bits (i.e. access rights) that would not be controlled by your application but by\n/// a future version of this crate instead.\n/// Because we cannot know what the effect on your application of an unknown restriction would be\n/// when handling an untested Landlock access right (i.e. denied-by-default access),\n/// it could trigger bugs in your application.\n///\n/// This crate provides a set of tools to sandbox as much as possible\n/// while guaranteeing a consistent behavior thanks to the [`Compatible`] methods.\n/// You should also test with different relevant kernel versions,\n/// see [landlock-test-tools](https://github.com/landlock-lsm/landlock-test-tools) and\n/// [CI integration](https://github.com/landlock-lsm/rust-landlock/pull/41).\n///\n/// This way, we can have the guarantee that the use of a set of tested Landlock ABI works as\n/// expected because features brought by newer Landlock ABI will never be enabled by default\n/// (cf. [Linux kernel compatibility contract](https://docs.kernel.org/userspace-api/landlock.html#compatibility)).\n///\n/// In a nutshell, test the access rights you request on a kernel that support them and\n/// on a kernel that doesn't support them.\n///\n/// Derived `Debug` formats are [not stable](https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability).\n#[cfg_attr(test, derive(EnumIter, EnumCountMacro))]\n#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]\n#[non_exhaustive]\npub enum ABI {\n    /// Kernel not supporting Landlock, either because it is not built with Landlock\n    /// or Landlock is not enabled at boot.\n    Unsupported = 0,\n    /// First Landlock ABI, introduced with\n    /// [Linux 5.13](https://git.kernel.org/stable/c/17ae69aba89dbfa2139b7f8024b757ab3cc42f59).\n    V1 = 1,\n    /// Second Landlock ABI, introduced with\n    /// [Linux 5.19](https://git.kernel.org/stable/c/cb44e4f061e16be65b8a16505e121490c66d30d0).\n    V2 = 2,\n    /// Third Landlock ABI, introduced with\n    /// [Linux 6.2](https://git.kernel.org/stable/c/299e2b1967578b1442128ba8b3e86ed3427d3651).\n    V3 = 3,\n    /// Fourth Landlock ABI, introduced with\n    /// [Linux 6.7](https://git.kernel.org/stable/c/136cc1e1f5be75f57f1e0404b94ee1c8792cb07d).\n    V4 = 4,\n    /// Fifth Landlock ABI, introduced with\n    /// [Linux 6.10](https://git.kernel.org/stable/c/2fc0e7892c10734c1b7c613ef04836d57d4676d5).\n    V5 = 5,\n    /// Sixth Landlock ABI, introduced with\n    /// [Linux 6.12](https://git.kernel.org/stable/c/e1b061b444fb01c237838f0d8238653afe6a8094).\n    V6 = 6,\n}\n\n// ABI should not be dynamically created (in other crates) according to the running kernel\n// to avoid inconsistent behaviors and non-determinism. Creating ABIs based on runtime detection\n// can lead to unreliable sandboxing where rules might differ between executions.\nimpl ABI {\n    #[cfg(test)]\n    fn is_known(value: i32) -> bool {\n        value > 0 && value < ABI::COUNT as i32\n    }\n}\n\n/// Converting from an integer to an ABI should only be used for testing.\n/// Indeed, manually setting the ABI can lead to inconsistent and unexpected behaviors.\n/// Instead, just use the appropriate access rights, this library will handle the rest.\nimpl From<i32> for ABI {\n    fn from(value: i32) -> ABI {\n        match value {\n            n if n <= 0 => ABI::Unsupported,\n            1 => ABI::V1,\n            2 => ABI::V2,\n            3 => ABI::V3,\n            4 => ABI::V4,\n            5 => ABI::V5,\n            // Returns the greatest known ABI.\n            _ => ABI::V6,\n        }\n    }\n}\n\n#[test]\nfn abi_from() {\n    // EOPNOTSUPP (-95), ENOSYS (-38)\n    for n in [-95, -38, -1, 0] {\n        assert_eq!(ABI::from(n), ABI::Unsupported);\n    }\n\n    let mut last_i = 1;\n    let mut last_abi = ABI::Unsupported;\n    for (i, abi) in ABI::iter().enumerate() {\n        last_i = i.try_into().unwrap();\n        last_abi = abi;\n        assert_eq!(ABI::from(last_i), last_abi);\n    }\n\n    assert_eq!(ABI::from(last_i + 1), last_abi);\n    assert_eq!(ABI::from(999), last_abi);\n}\n\n#[test]\nfn known_abi() {\n    assert!(!ABI::is_known(-1));\n    assert!(!ABI::is_known(0));\n    assert!(!ABI::is_known(999));\n\n    let mut last_i = -1;\n    for (i, _) in ABI::iter().enumerate().skip(1) {\n        last_i = i as i32;\n        assert!(ABI::is_known(last_i));\n    }\n    assert!(!ABI::is_known(last_i + 1));\n}\n\nimpl Display for ABI {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        match self {\n            ABI::Unsupported => write!(f, \"unsupported\"),\n            v => (*v as u32).fmt(f),\n        }\n    }\n}\n\n/// Status of Landlock support for the running system.\n///\n/// This enum is used to represent the status of the Landlock support for the system where the code\n/// is executed. It can indicate whether Landlock is available or not.\n///\n/// # Warning\n///\n/// Sandboxed programs should only use this data to log or provide information to users,\n/// not to change their behavior according to this status.  Indeed, the `Ruleset` and the other\n/// types are designed to handle the compatibility in a simple and safe way.\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub enum LandlockStatus {\n    /// Landlock is supported but not enabled (`EOPNOTSUPP`).\n    NotEnabled,\n    /// Landlock is not implemented (i.e. not built into the running kernel: `ENOSYS`).\n    NotImplemented,\n    /// Landlock is available and working on the running system.\n    ///\n    /// This indicates that the kernel supports Landlock and it's properly enabled.\n    /// The crate uses the `effective_abi` for all operations, which represents\n    /// the highest ABI version that both the kernel and this crate understand.\n    Available {\n        /// The effective ABI version that this crate will use for Landlock operations.\n        /// This is the intersection of what the kernel supports and what this crate knows about.\n        effective_abi: ABI,\n        /// The actual kernel ABI version when it's newer than any ABI supported by this crate.\n        ///\n        /// If `Some(version)`, it means the running kernel supports Landlock ABI `version`\n        /// which is higher than the latest ABI known by this crate.\n        ///\n        /// This field is purely informational and is never used for Landlock operations.\n        /// The crate always and only uses `effective_abi` for all functionality.\n        kernel_abi: Option<i32>,\n    },\n}\n\nimpl LandlockStatus {\n    // Must remain private to avoid inconsistent behavior using such unknown-at-build-time ABI\n    // e.g., AccessFs::from_all(ABI::new_current())\n    //\n    // This should not be Default::default() because the returned value would may not be the same\n    // for all users.\n    fn current() -> Self {\n        // Landlock ABI version starts at 1 but errno is only set for negative values.\n        let v = unsafe {\n            uapi::landlock_create_ruleset(\n                std::ptr::null(),\n                0,\n                uapi::LANDLOCK_CREATE_RULESET_VERSION,\n            )\n        };\n        if v < 0 {\n            // The only possible error values should be EOPNOTSUPP and ENOSYS.\n            match Error::last_os_error().raw_os_error() {\n                Some(libc::EOPNOTSUPP) => Self::NotEnabled,\n                _ => Self::NotImplemented,\n            }\n        } else {\n            let abi = ABI::from(v);\n            Self::Available {\n                effective_abi: abi,\n                kernel_abi: (v != abi as i32).then_some(v),\n            }\n        }\n    }\n}\n\n// Test against the running kernel.\n#[test]\nfn test_current_landlock_status() {\n    let status = LandlockStatus::current();\n    if *TEST_ABI == ABI::Unsupported {\n        assert_eq!(status, LandlockStatus::NotImplemented);\n    } else {\n        assert!(\n            matches!(status, LandlockStatus::Available { effective_abi, .. } if effective_abi == *TEST_ABI)\n        );\n        if std::env::var(TEST_ABI_ENV_NAME).is_ok() {\n            // We cannot reliably check for unknown kernel.\n            assert!(matches!(\n                status,\n                LandlockStatus::Available {\n                    kernel_abi: None,\n                    ..\n                }\n            ));\n        }\n    }\n}\n\nimpl From<LandlockStatus> for ABI {\n    fn from(status: LandlockStatus) -> Self {\n        match status {\n            // The only possible error values should be EOPNOTSUPP and ENOSYS,\n            // but let's convert all kind of errors as unsupported.\n            LandlockStatus::NotEnabled | LandlockStatus::NotImplemented => ABI::Unsupported,\n            LandlockStatus::Available { effective_abi, .. } => effective_abi,\n        }\n    }\n}\n\n// This is only useful to tests and should not be exposed publicly because\n// the mapping can only be partial.\n#[cfg(test)]\nimpl From<ABI> for LandlockStatus {\n    fn from(abi: ABI) -> Self {\n        match abi {\n            // Convert to ENOSYS because of check_ruleset_support() and ruleset_unsupported() tests.\n            ABI::Unsupported => Self::NotImplemented,\n            _ => Self::Available {\n                effective_abi: abi,\n                kernel_abi: None,\n            },\n        }\n    }\n}\n\n#[cfg(test)]\npub(crate) static TEST_ABI_ENV_NAME: &str = \"LANDLOCK_CRATE_TEST_ABI\";\n\n#[cfg(test)]\nlazy_static! {\n    pub(crate) static ref TEST_ABI: ABI = match std::env::var(\"LANDLOCK_CRATE_TEST_ABI\") {\n        Ok(s) => {\n            let n = s.parse::<i32>().unwrap();\n            if ABI::is_known(n) || n == 0 {\n                ABI::from(n)\n            } else {\n                panic!(\"Unknown ABI: {n}\");\n            }\n        }\n        Err(std::env::VarError::NotPresent) => LandlockStatus::current().into(),\n        Err(e) => panic!(\"Failed to read LANDLOCK_CRATE_TEST_ABI: {e}\"),\n    };\n}\n\n#[cfg(test)]\npub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support: Option<ABI>) -> bool {\n    mock < partial_support\n        || mock <= *TEST_ABI\n        || if let Some(full) = full_support {\n            full <= *TEST_ABI\n        } else {\n            partial_support <= *TEST_ABI\n        }\n}\n\n#[cfg(test)]\npub(crate) fn get_errno_from_landlock_status() -> Option<i32> {\n    match LandlockStatus::current() {\n        LandlockStatus::NotImplemented | LandlockStatus::NotEnabled => {\n            match Error::last_os_error().raw_os_error() {\n                // Returns ENOSYS when the kernel is not built with Landlock support,\n                // or EOPNOTSUPP when Landlock is supported but disabled at boot time.\n                ret @ Some(libc::ENOSYS | libc::EOPNOTSUPP) => ret,\n                // Other values can only come from bogus seccomp filters or debugging tampering.\n                ret => {\n                    eprintln!(\"Current kernel should support this Landlock ABI according to $LANDLOCK_CRATE_TEST_ABI\");\n                    eprintln!(\"Unexpected result: {ret:?}\");\n                    unreachable!();\n                }\n            }\n        }\n        LandlockStatus::Available { .. } => None,\n    }\n}\n\n#[test]\nfn current_kernel_abi() {\n    // Ensures that the tested Landlock ABI is the latest known version supported by the running\n    // kernel.  If this test failed, you need set the LANDLOCK_CRATE_TEST_ABI environment variable\n    // to the Landlock ABI version supported by your kernel.  With a missing variable, the latest\n    // Landlock ABI version known by this crate is automatically set.\n    // From Linux 5.13 to 5.18, you need to run: LANDLOCK_CRATE_TEST_ABI=1 cargo test\n    let test_abi = *TEST_ABI;\n    let current_abi = LandlockStatus::current().into();\n    println!(\n        \"Current kernel version: {}\",\n        std::fs::read_to_string(\"/proc/version\")\n            .unwrap_or_else(|_| \"unknown\".into())\n            .trim()\n    );\n    println!(\"Expected Landlock ABI {test_abi:?} whereas the current ABI is {current_abi:#?}\");\n    assert_eq!(test_abi, current_abi);\n}\n\n// CompatState is not public outside this crate.\n/// Returned by ruleset builder.\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub enum CompatState {\n    /// Initial undefined state.\n    Init,\n    /// All requested restrictions are enforced.\n    Full,\n    /// Some requested restrictions are enforced, following a best-effort approach.\n    Partial,\n    /// The running system doesn't support Landlock.\n    No,\n    /// Final unsupported state.\n    Dummy,\n}\n\nimpl CompatState {\n    fn update(&mut self, other: Self) {\n        *self = match (*self, other) {\n            (CompatState::Init, other) => other,\n            (CompatState::Dummy, _) => CompatState::Dummy,\n            (_, CompatState::Dummy) => CompatState::Dummy,\n            (CompatState::No, CompatState::No) => CompatState::No,\n            (CompatState::Full, CompatState::Full) => CompatState::Full,\n            (_, _) => CompatState::Partial,\n        }\n    }\n}\n\n#[test]\nfn compat_state_update_1() {\n    let mut state = CompatState::Full;\n\n    state.update(CompatState::Full);\n    assert_eq!(state, CompatState::Full);\n\n    state.update(CompatState::No);\n    assert_eq!(state, CompatState::Partial);\n\n    state.update(CompatState::Full);\n    assert_eq!(state, CompatState::Partial);\n\n    state.update(CompatState::Full);\n    assert_eq!(state, CompatState::Partial);\n\n    state.update(CompatState::No);\n    assert_eq!(state, CompatState::Partial);\n\n    state.update(CompatState::Dummy);\n    assert_eq!(state, CompatState::Dummy);\n\n    state.update(CompatState::Full);\n    assert_eq!(state, CompatState::Dummy);\n}\n\n#[test]\nfn compat_state_update_2() {\n    let mut state = CompatState::Full;\n\n    state.update(CompatState::Full);\n    assert_eq!(state, CompatState::Full);\n\n    state.update(CompatState::No);\n    assert_eq!(state, CompatState::Partial);\n\n    state.update(CompatState::Full);\n    assert_eq!(state, CompatState::Partial);\n}\n\n#[cfg_attr(test, derive(PartialEq))]\n#[derive(Copy, Clone, Debug)]\npub(crate) struct Compatibility {\n    status: LandlockStatus,\n    pub(crate) level: Option<CompatLevel>,\n    pub(crate) state: CompatState,\n}\n\nimpl From<LandlockStatus> for Compatibility {\n    fn from(status: LandlockStatus) -> Self {\n        Compatibility {\n            status,\n            level: Default::default(),\n            state: CompatState::Init,\n        }\n    }\n}\n\n#[cfg(test)]\nimpl From<ABI> for Compatibility {\n    fn from(abi: ABI) -> Self {\n        Self::from(LandlockStatus::from(abi))\n    }\n}\n\nimpl Compatibility {\n    // Compatibility is a semi-opaque struct.\n    #[allow(clippy::new_without_default)]\n    pub(crate) fn new() -> Self {\n        LandlockStatus::current().into()\n    }\n\n    pub(crate) fn update(&mut self, state: CompatState) {\n        self.state.update(state);\n    }\n\n    pub(crate) fn abi(&self) -> ABI {\n        self.status.into()\n    }\n\n    pub(crate) fn status(&self) -> LandlockStatus {\n        self.status\n    }\n}\n\npub(crate) mod private {\n    use crate::CompatLevel;\n\n    pub trait OptionCompatLevelMut {\n        fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel>;\n    }\n}\n\n/// Properly handles runtime unsupported features.\n///\n/// This guarantees consistent behaviors across crate users\n/// and runtime kernels even if this crate get new features.\n/// It eases backward compatibility and enables future-proofness.\n///\n/// Landlock is a security feature designed to help improve security of a running system\n/// thanks to application developers.\n/// To protect users as much as possible,\n/// compatibility with the running system should then be handled in a best-effort way,\n/// contrary to common system features.\n/// In some circumstances\n/// (e.g. applications carefully designed to only be run with a specific set of kernel features),\n/// it may be required to error out if some of these features are not available\n/// and will then not be enforced.\npub trait Compatible: Sized + private::OptionCompatLevelMut {\n    /// To enable a best-effort security approach,\n    /// Landlock features that are not supported by the running system\n    /// are silently ignored by default,\n    /// which is a sane choice for most use cases.\n    /// However, on some rare circumstances,\n    /// developers may want to have some guarantees that their applications\n    /// will not run if a certain level of sandboxing is not possible.\n    /// If we really want to error out when not all our requested requirements are met,\n    /// then we can configure it with `set_compatibility()`.\n    ///\n    /// The `Compatible` trait is implemented for all object builders\n    /// (e.g. [`Ruleset`](crate::Ruleset)).\n    /// Such builders have a set of methods to incrementally build an object.\n    /// These build methods rely on kernel features that may not be available at runtime.\n    /// The `set_compatibility()` method enables to control the effect of\n    /// the following build method calls starting after the `set_compatibility()` call.\n    /// Such effect can be:\n    /// * to silently ignore unsupported features\n    ///   and continue building ([`CompatLevel::BestEffort`]);\n    /// * to silently ignore unsupported features\n    ///   and ignore the whole build ([`CompatLevel::SoftRequirement`]);\n    /// * to return an error for any unsupported feature ([`CompatLevel::HardRequirement`]).\n    ///\n    /// Taking [`Ruleset`](crate::Ruleset) as an example,\n    /// the [`handle_access()`](crate::RulesetAttr::handle_access()) build method\n    /// returns a [`Result`] that can be [`Err(RulesetError)`](crate::RulesetError)\n    /// with a nested [`CompatError`].\n    /// Such error can only occur with a running Linux kernel not supporting the requested\n    /// Landlock accesses *and* if the current compatibility level is\n    /// [`CompatLevel::HardRequirement`].\n    /// However, such error is not possible with [`CompatLevel::BestEffort`]\n    /// nor [`CompatLevel::SoftRequirement`].\n    ///\n    /// The order of this call is important because\n    /// it defines the behavior of the following build method calls that return a [`Result`].\n    /// If `set_compatibility(CompatLevel::HardRequirement)` is called on an object,\n    /// then a [`CompatError`] may be returned for the next method calls,\n    /// until the next call to `set_compatibility()`.\n    /// This enables to change the behavior of a set of build method calls,\n    /// for instance to be sure that the sandbox will at least restrict some access rights.\n    ///\n    /// New objects inherit the compatibility configuration of their parents, if any.\n    /// For instance, [`Ruleset::create()`](crate::Ruleset::create()) returns\n    /// a [`RulesetCreated`](crate::RulesetCreated) object that inherits the\n    /// `Ruleset`'s compatibility configuration.\n    ///\n    /// # Example with `SoftRequirement`\n    ///\n    /// Let's say an application legitimately needs to rename files between directories.\n    /// Because of [previous Landlock limitations](https://docs.kernel.org/userspace-api/landlock.html#file-renaming-and-linking-abi-2),\n    /// this was forbidden with the [first version of Landlock](ABI::V1),\n    /// but it is now handled starting with the [second version](ABI::V2).\n    /// For this use case, we only want the application to be sandboxed\n    /// if we have the guarantee that it will not break a legitimate usage (i.e. rename files).\n    /// We then create a ruleset which will either support file renaming\n    /// (thanks to [`AccessFs::Refer`](crate::AccessFs::Refer)) or silently do nothing.\n    ///\n    /// ```\n    /// use landlock::*;\n    ///\n    /// fn ruleset_handling_renames() -> Result<RulesetCreated, RulesetError> {\n    ///     Ok(Ruleset::default()\n    ///         // This ruleset must either handle the AccessFs::Refer right,\n    ///         // or it must silently ignore the whole sandboxing.\n    ///         .set_compatibility(CompatLevel::SoftRequirement)\n    ///         .handle_access(AccessFs::Refer)?\n    ///         // However, this ruleset may also handle other (future) access rights\n    ///         // if they are supported by the running kernel.\n    ///         .set_compatibility(CompatLevel::BestEffort)\n    ///         .handle_access(AccessFs::from_all(ABI::V6))?\n    ///         .create()?)\n    /// }\n    /// ```\n    ///\n    /// # Example with `HardRequirement`\n    ///\n    /// Security-dedicated applications may want to ensure that\n    /// an untrusted software component is subject to a minimum of restrictions before launching it.\n    /// In this case, we want to create a ruleset which will at least support\n    /// all restrictions provided by the [first version of Landlock](ABI::V1),\n    /// and opportunistically handle restrictions supported by newer kernels.\n    ///\n    /// ```\n    /// use landlock::*;\n    ///\n    /// fn ruleset_fragile() -> Result<RulesetCreated, RulesetError> {\n    ///     Ok(Ruleset::default()\n    ///         // This ruleset must either handle at least all accesses defined by\n    ///         // the first Landlock version (e.g. AccessFs::WriteFile),\n    ///         // or the following handle_access() call must return a wrapped\n    ///         // AccessError<AccessFs>::Incompatible error.\n    ///         .set_compatibility(CompatLevel::HardRequirement)\n    ///         .handle_access(AccessFs::from_all(ABI::V1))?\n    ///         // However, this ruleset may also handle new access rights\n    ///         // (e.g. AccessFs::Refer defined by the second version of Landlock)\n    ///         // if they are supported by the running kernel,\n    ///         // but without returning any error otherwise.\n    ///         .set_compatibility(CompatLevel::BestEffort)\n    ///         .handle_access(AccessFs::from_all(ABI::V6))?\n    ///         .create()?)\n    /// }\n    /// ```\n    fn set_compatibility(mut self, level: CompatLevel) -> Self {\n        *self.as_option_compat_level_mut() = Some(level);\n        self\n    }\n\n    /// Cf. [`set_compatibility()`](Compatible::set_compatibility()):\n    ///\n    /// - `set_best_effort(true)` translates to `set_compatibility(CompatLevel::BestEffort)`.\n    ///\n    /// - `set_best_effort(false)` translates to `set_compatibility(CompatLevel::HardRequirement)`.\n    #[deprecated(note = \"Use set_compatibility() instead\")]\n    fn set_best_effort(self, best_effort: bool) -> Self\n    where\n        Self: Sized,\n    {\n        self.set_compatibility(match best_effort {\n            true => CompatLevel::BestEffort,\n            false => CompatLevel::HardRequirement,\n        })\n    }\n}\n\n#[test]\n#[allow(deprecated)]\nfn deprecated_set_best_effort() {\n    use crate::{CompatLevel, Compatible, Ruleset};\n\n    assert_eq!(\n        Ruleset::default().set_best_effort(true).compat,\n        Ruleset::default()\n            .set_compatibility(CompatLevel::BestEffort)\n            .compat\n    );\n    assert_eq!(\n        Ruleset::default().set_best_effort(false).compat,\n        Ruleset::default()\n            .set_compatibility(CompatLevel::HardRequirement)\n            .compat\n    );\n}\n\n/// See the [`Compatible`] documentation.\n#[cfg_attr(test, derive(EnumIter))]\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\npub enum CompatLevel {\n    /// Takes into account the build requests if they are supported by the running system,\n    /// or silently ignores them otherwise.\n    /// Never returns a compatibility error.\n    #[default]\n    BestEffort,\n    /// Takes into account the build requests if they are supported by the running system,\n    /// or silently ignores the whole build object otherwise.\n    /// Never returns a compatibility error.\n    /// If not supported,\n    /// the call to [`RulesetCreated::restrict_self()`](crate::RulesetCreated::restrict_self())\n    /// will return a\n    /// [`RestrictionStatus { ruleset: RulesetStatus::NotEnforced, no_new_privs: false, }`](crate::RestrictionStatus).\n    SoftRequirement,\n    /// Takes into account the build requests if they are supported by the running system,\n    /// or returns a compatibility error otherwise ([`CompatError`]).\n    HardRequirement,\n}\n\nimpl From<Option<CompatLevel>> for CompatLevel {\n    fn from(opt: Option<CompatLevel>) -> Self {\n        match opt {\n            None => CompatLevel::default(),\n            Some(ref level) => *level,\n        }\n    }\n}\n\n// TailoredCompatLevel could be replaced with AsMut<Option<CompatLevel>>, but only traits defined\n// in the current crate can be implemented for types defined outside of the crate.  Furthermore it\n// provides a default implementation which is handy for types such as BitFlags.\npub trait TailoredCompatLevel {\n    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel\n    where\n        L: Into<CompatLevel>,\n    {\n        parent_level.into()\n    }\n}\n\nimpl<T> TailoredCompatLevel for T\nwhere\n    Self: Compatible,\n{\n    // Every Compatible trait implementation returns its own compatibility level, if set.\n    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel\n    where\n        L: Into<CompatLevel>,\n    {\n        // Using a mutable reference is not required but it makes the code simpler (no double AsRef\n        // implementations for each Compatible types), and more importantly it guarantees\n        // consistency with Compatible::set_compatibility().\n        match self.as_option_compat_level_mut() {\n            None => parent_level.into(),\n            // Returns the most constrained compatibility level.\n            Some(ref level) => parent_level.into().max(*level),\n        }\n    }\n}\n\n#[test]\nfn tailored_compat_level() {\n    use crate::{AccessFs, PathBeneath, PathFd};\n\n    fn new_path(level: CompatLevel) -> PathBeneath<PathFd> {\n        PathBeneath::new(PathFd::new(\"/\").unwrap(), AccessFs::Execute).set_compatibility(level)\n    }\n\n    for parent_level in CompatLevel::iter() {\n        assert_eq!(\n            new_path(CompatLevel::BestEffort).tailored_compat_level(parent_level),\n            parent_level\n        );\n        assert_eq!(\n            new_path(CompatLevel::HardRequirement).tailored_compat_level(parent_level),\n            CompatLevel::HardRequirement\n        );\n    }\n\n    assert_eq!(\n        new_path(CompatLevel::SoftRequirement).tailored_compat_level(CompatLevel::SoftRequirement),\n        CompatLevel::SoftRequirement\n    );\n\n    for child_level in CompatLevel::iter() {\n        assert_eq!(\n            new_path(child_level).tailored_compat_level(CompatLevel::BestEffort),\n            child_level\n        );\n        assert_eq!(\n            new_path(child_level).tailored_compat_level(CompatLevel::HardRequirement),\n            CompatLevel::HardRequirement\n        );\n    }\n}\n\n// CompatResult is not public outside this crate.\npub enum CompatResult<A>\nwhere\n    A: Access,\n{\n    // Fully matches the request.\n    Full,\n    // Partially matches the request.\n    Partial(CompatError<A>),\n    // Doesn't matches the request.\n    No(CompatError<A>),\n}\n\n// TryCompat is not public outside this crate.\npub trait TryCompat<A>\nwhere\n    Self: Sized + TailoredCompatLevel,\n    A: Access,\n{\n    fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>>;\n\n    // Default implementation for objects without children.\n    //\n    // If returning something other than Ok(Some(self)), the implementation must use its own\n    // compatibility level, if any, with self.tailored_compat_level(default_compat_level), and pass\n    // it with the abi and compat_state to each child.try_compat().  See PathBeneath implementation\n    // and the self.allowed_access.try_compat() call.\n    //\n    // # Warning\n    //\n    // Errors must be prioritized over incompatibility (i.e. return Err(e) over Ok(None)) for all\n    // children.\n    fn try_compat_children<L>(\n        self,\n        _abi: ABI,\n        _parent_level: L,\n        _compat_state: &mut CompatState,\n    ) -> Result<Option<Self>, CompatError<A>>\n    where\n        L: Into<CompatLevel>,\n    {\n        Ok(Some(self))\n    }\n\n    // Update compat_state and return an error according to try_compat_*() error, or to the\n    // compatibility level, i.e. either route compatible object or error.\n    fn try_compat<L>(\n        mut self,\n        abi: ABI,\n        parent_level: L,\n        compat_state: &mut CompatState,\n    ) -> Result<Option<Self>, CompatError<A>>\n    where\n        L: Into<CompatLevel>,\n    {\n        let compat_level = self.tailored_compat_level(parent_level);\n        let some_inner = match self.try_compat_inner(abi) {\n            Ok(CompatResult::Full) => {\n                compat_state.update(CompatState::Full);\n                true\n            }\n            Ok(CompatResult::Partial(error)) => match compat_level {\n                CompatLevel::BestEffort => {\n                    compat_state.update(CompatState::Partial);\n                    true\n                }\n                CompatLevel::SoftRequirement => {\n                    compat_state.update(CompatState::Dummy);\n                    false\n                }\n                CompatLevel::HardRequirement => {\n                    compat_state.update(CompatState::Dummy);\n                    return Err(error);\n                }\n            },\n            Ok(CompatResult::No(error)) => match compat_level {\n                CompatLevel::BestEffort => {\n                    compat_state.update(CompatState::No);\n                    false\n                }\n                CompatLevel::SoftRequirement => {\n                    compat_state.update(CompatState::Dummy);\n                    false\n                }\n                CompatLevel::HardRequirement => {\n                    compat_state.update(CompatState::Dummy);\n                    return Err(error);\n                }\n            },\n            Err(error) => {\n                // Safeguard to help for test consistency.\n                compat_state.update(CompatState::Dummy);\n                return Err(error);\n            }\n        };\n\n        // At this point, any inner error have been returned, so we can proceed with\n        // try_compat_children()?.\n        match self.try_compat_children(abi, compat_level, compat_state)? {\n            Some(n) if some_inner => Ok(Some(n)),\n            _ => Ok(None),\n        }\n    }\n}\n"
  },
  {
    "path": "src/errata.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::compat::ABI;\nuse crate::{uapi, BitFlags};\nuse enumflags2::bitflags;\n\n/// Fixed kernel issues for the running Landlock implementation.\n///\n/// Each variant represents a specific bug fix that may have been\n/// backported to the running kernel.  Use [`Erratum::current()`]\n/// before building a [`Ruleset`](crate::Ruleset) to decide which\n/// features are safe to use.\n///\n/// An [`ABI`] version can be converted into the set of applicable errata\n/// with `BitFlags::<Erratum>::from(abi)`.\n///\n/// # Warning\n///\n/// Most applications should **not** check errata.  Disabling a sandboxing\n/// feature because an erratum is not fixed could leave the system **less**\n/// secure than using Landlock's best-effort protection with the buggy\n/// feature enabled.  Errata should only be used to **add** features\n/// (e.g., enabling a restriction only when its bug is confirmed fixed),\n/// never to remove them.\n#[bitflags]\n#[repr(u32)]\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub enum Erratum {\n    /// Erratum 1 (ABI 4): non-TCP stream sockets (SMC, MPTCP, SCTP)\n    /// were incorrectly restricted by TCP access rights during\n    /// `bind(2)` and `connect(2)`.\n    ///\n    /// Affects [`crate::AccessNet::BindTcp`] and [`crate::AccessNet::ConnectTcp`].\n    ///\n    /// See [erratum 1](https://docs.kernel.org/userspace-api/landlock.html#erratum-1-tcp-socket-identification).\n    TcpSocketIdentification = 1 << 0,\n    /// Erratum 2 (ABI 6): signal scoping was overly restrictive,\n    /// preventing sandboxed threads from signaling other threads\n    /// within the same process in different domains.\n    ///\n    /// Affects [`crate::Scope::Signal`].\n    ///\n    /// See [erratum 2](https://docs.kernel.org/userspace-api/landlock.html#erratum-2-scoped-signal-handling).\n    ScopedSignalHandling = 1 << 1,\n    /// Erratum 3 (ABI 1): access rights could be widened through\n    /// rename or link actions on disconnected directories under\n    /// bind mounts, potentially bypassing `LANDLOCK_ACCESS_FS_REFER`\n    /// restrictions.\n    ///\n    /// See [erratum 3](https://docs.kernel.org/userspace-api/landlock.html#erratum-3-disconnected-directory-handling).\n    DisconnectedDirectoryHandling = 1 << 2,\n}\n\nimpl Erratum {\n    /// Queries the running kernel for fixed errata.\n    ///\n    /// Returns a bitmask of errata that have been fixed in the running\n    /// kernel.  Unknown errata bits from newer kernels are preserved.\n    /// Returns empty if the kernel doesn't support the errata interface.\n    pub fn current() -> BitFlags<Self> {\n        let ret = unsafe {\n            uapi::landlock_create_ruleset(std::ptr::null(), 0, uapi::LANDLOCK_CREATE_RULESET_ERRATA)\n        };\n        if ret >= 0 {\n            // SAFETY: The kernel may return bits unknown to this crate version.\n            // Using from_bits_unchecked to preserve them.\n            unsafe { BitFlags::from_bits_unchecked(ret as u32) }\n        } else {\n            BitFlags::empty()\n        }\n    }\n}\n\n/// Converts an [`ABI`] version into the set of errata applicable to that ABI.\n///\n/// An erratum is applicable if the ABI includes the feature affected by the bug.\n/// For example, [`Erratum::TcpSocketIdentification`] is only applicable to\n/// [`ABI::V4`] and later, since TCP access rights were introduced in that version.\n///\n/// Uses the same incremental accumulation pattern as\n/// [`AccessFs::from_write()`](crate::AccessFs::from_write).\n///\n/// # Stability\n///\n/// The set of errata returned for a given ABI may grow in future versions\n/// of this crate as new kernel bug fixes are identified and backported.\n/// Do not rely on the exact set being stable across crate versions.\nimpl From<ABI> for BitFlags<Erratum> {\n    fn from(abi: ABI) -> Self {\n        match abi {\n            ABI::Unsupported => BitFlags::empty(),\n            // Erratum 3: disconnected directory handling (FS, ABI 1+).\n            ABI::V1 | ABI::V2 | ABI::V3 => Erratum::DisconnectedDirectoryHandling.into(),\n            // Erratum 1: TCP socket identification (net, ABI 4+).\n            ABI::V4 | ABI::V5 => Self::from(ABI::V3) | Erratum::TcpSocketIdentification,\n            // Erratum 2: scoped signal handling (scopes, ABI 6+).\n            // When adding a new ABI version without new errata, append it here.\n            ABI::V6 => Self::from(ABI::V5) | Erratum::ScopedSignalHandling,\n        }\n    }\n}\n\n/// Returns the set of errata that have not been backported yet for a given ABI.\n///\n/// This is the single source of truth for known backport gaps.  When an\n/// erratum is backported to a kernel version, remove it from the\n/// corresponding match arm.  The CI will catch mismatches.\n#[cfg(test)]\nfn not_backported_yet(abi: ABI) -> BitFlags<Erratum> {\n    match abi {\n        ABI::Unsupported => BitFlags::empty(),\n        // TODO: erratum 3 (DisconnectedDirectoryHandling) should be backported.\n        ABI::V1 | ABI::V2 => Erratum::DisconnectedDirectoryHandling.into(),\n        // 6.4, 6.7, 6.10: EOL, no errata interface on stable.kernel.\n        ABI::V3 | ABI::V4 | ABI::V5 => BitFlags::empty(),\n        // 6.12: all errata backported.\n        ABI::V6 => BitFlags::empty(),\n    }\n}\n\n#[test]\nfn errata_query() {\n    // Verifies the syscall wrapper works on any kernel.\n    let _errata = Erratum::current();\n}\n\n#[test]\nfn errata_up_to_date() {\n    use crate::compat::{ABI, TEST_ABI, TEST_ABI_ENV_NAME};\n\n    // This test requires LANDLOCK_CRATE_TEST_ABI to be explicitly set because\n    // the errata assertions are tied to specific CI kernel versions.  Without\n    // it, TEST_ABI is auto-detected from the running kernel, but From<i32>\n    // maps unknown ABI versions to the highest known one, making the\n    // ABI-to-kernel mapping ambiguous (e.g., a 6.15 kernel maps to V6 before\n    // ABI::V7 exists).  Since Erratum::current() queries the real kernel, the\n    // expected errata for the declared ABI may not match.\n    if std::env::var(TEST_ABI_ENV_NAME).is_err() {\n        eprintln!(\"Skipping errata_up_to_date: {} not set\", TEST_ABI_ENV_NAME,);\n        return;\n    }\n\n    let current = Erratum::current();\n    let applicable: BitFlags<Erratum> = (*TEST_ABI).into();\n    let expected = applicable & !not_backported_yet(*TEST_ABI);\n\n    // Kernel must never report errata for features absent from this ABI.\n    assert!(\n        current & !applicable == BitFlags::empty(),\n        \"kernel reported errata not applicable to ABI {:?}: {:?}\",\n        *TEST_ABI,\n        current & !applicable,\n    );\n\n    match *TEST_ABI {\n        ABI::Unsupported => assert!(current.is_empty()),\n        ABI::V1 | ABI::V2 => assert_eq!(current, expected),\n        // 6.4, 6.7, 6.10: EOL, no errata interface on stable.kernel.\n        ABI::V3 | ABI::V4 | ABI::V5 => {}\n        ABI::V6 => assert_eq!(current, expected),\n    }\n}\n"
  },
  {
    "path": "src/errors.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::{Access, AccessFs, AccessNet, BitFlags, HandledAccess, PrivateHandledAccess, Scope};\nuse libc::c_int;\nuse std::io;\nuse std::path::PathBuf;\nuse thiserror::Error;\n\n/// Maps to all errors that can be returned by a ruleset action.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum RulesetError {\n    #[error(transparent)]\n    HandleAccesses(#[from] HandleAccessesError),\n    #[error(transparent)]\n    CreateRuleset(#[from] CreateRulesetError),\n    #[error(transparent)]\n    AddRules(#[from] AddRulesError),\n    #[error(transparent)]\n    RestrictSelf(#[from] RestrictSelfError),\n    #[error(transparent)]\n    Scope(#[from] ScopeError),\n}\n\n#[test]\nfn ruleset_error_breaking_change() {\n    use crate::*;\n\n    // Generics are part of the API and modifying them can lead to a breaking change.\n    let _: RulesetError = RulesetError::HandleAccesses(HandleAccessesError::Fs(\n        HandleAccessError::Compat(CompatError::Access(AccessError::Empty)),\n    ));\n}\n\n/// Identifies errors when updating the ruleset's handled access-rights.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum HandleAccessError<T>\nwhere\n    T: HandledAccess,\n{\n    #[error(transparent)]\n    Compat(#[from] CompatError<T>),\n}\n\n/// Identifies errors when updating the ruleset's scopes.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum ScopeError {\n    #[error(transparent)]\n    Compat(#[from] CompatError<Scope>),\n}\n\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum HandleAccessesError {\n    #[error(transparent)]\n    Fs(HandleAccessError<AccessFs>),\n    #[error(transparent)]\n    Net(HandleAccessError<AccessNet>),\n}\n\n// Generically implement for all the handled access implementations rather than for the cases\n// listed in HandleAccessesError (with #[from]).\nimpl<A> From<HandleAccessError<A>> for HandleAccessesError\nwhere\n    A: PrivateHandledAccess,\n{\n    fn from(error: HandleAccessError<A>) -> Self {\n        A::into_handle_accesses_error(error)\n    }\n}\n\n/// Identifies errors when creating a ruleset.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum CreateRulesetError {\n    /// The `landlock_create_ruleset()` system call failed.\n    #[error(\"failed to create a ruleset: {source}\")]\n    #[non_exhaustive]\n    CreateRulesetCall { source: io::Error },\n    /// Missing call to [`RulesetAttr::handle_access()`](crate::RulesetAttr::handle_access)\n    /// or [`RulesetAttr::scope()`](crate::RulesetAttr::scope).\n    #[error(\"missing access\")]\n    MissingHandledAccess,\n}\n\n/// Identifies errors when adding a rule to a ruleset.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum AddRuleError<T>\nwhere\n    T: HandledAccess,\n{\n    /// The `landlock_add_rule()` system call failed.\n    #[error(\"failed to add a rule: {source}\")]\n    #[non_exhaustive]\n    AddRuleCall { source: io::Error },\n    /// The rule's access-rights are not all handled by the (requested) ruleset access-rights.\n    #[error(\"access-rights not handled by the ruleset: {incompatible:?}\")]\n    UnhandledAccess {\n        access: BitFlags<T>,\n        incompatible: BitFlags<T>,\n    },\n    #[error(transparent)]\n    Compat(#[from] CompatError<T>),\n}\n\n// Generically implement for all the handled access implementations rather than for the cases listed\n// in AddRulesError (with #[from]).\nimpl<A> From<AddRuleError<A>> for AddRulesError\nwhere\n    A: PrivateHandledAccess,\n{\n    fn from(error: AddRuleError<A>) -> Self {\n        A::into_add_rules_error(error)\n    }\n}\n\n/// Identifies errors when adding rules to a ruleset thanks to an iterator returning\n/// Result<Rule, E> items.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum AddRulesError {\n    #[error(transparent)]\n    Fs(AddRuleError<AccessFs>),\n    #[error(transparent)]\n    Net(AddRuleError<AccessNet>),\n}\n\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum CompatError<T>\nwhere\n    T: Access,\n{\n    #[error(transparent)]\n    PathBeneath(#[from] PathBeneathError),\n    #[error(transparent)]\n    Access(#[from] AccessError<T>),\n}\n\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum PathBeneathError {\n    /// To check that access-rights are consistent with a file descriptor, a call to\n    /// [`RulesetCreatedAttr::add_rule()`](crate::RulesetCreatedAttr::add_rule)\n    /// looks at the file type with an `fstat()` system call.\n    #[error(\"failed to check file descriptor type: {source}\")]\n    #[non_exhaustive]\n    StatCall { source: io::Error },\n    /// This error is returned by\n    /// [`RulesetCreatedAttr::add_rule()`](crate::RulesetCreatedAttr::add_rule)\n    /// if the related PathBeneath object is not set to best-effort,\n    /// and if its allowed access-rights contain directory-only ones\n    /// whereas the file descriptor doesn't point to a directory.\n    #[error(\"incompatible directory-only access-rights: {incompatible:?}\")]\n    DirectoryAccess {\n        access: BitFlags<AccessFs>,\n        incompatible: BitFlags<AccessFs>,\n    },\n}\n\n#[derive(Debug, Error)]\n// Exhaustive enum\npub enum AccessError<T>\nwhere\n    T: Access,\n{\n    /// The access-rights set is empty, which doesn't make sense and would be rejected by the\n    /// kernel.\n    #[error(\"empty access-right\")]\n    Empty,\n    /// The access-rights set was forged with the unsafe `BitFlags::from_bits_unchecked()` and it\n    /// contains unknown bits.\n    #[error(\"unknown access-rights (at build time): {unknown:?}\")]\n    Unknown {\n        access: BitFlags<T>,\n        unknown: BitFlags<T>,\n    },\n    /// The best-effort approach was (deliberately) disabled and the requested access-rights are\n    /// fully incompatible with the running kernel.\n    #[error(\"fully incompatible access-rights: {access:?}\")]\n    Incompatible { access: BitFlags<T> },\n    /// The best-effort approach was (deliberately) disabled and the requested access-rights are\n    /// partially incompatible with the running kernel.\n    #[error(\"partially incompatible access-rights: {incompatible:?}\")]\n    PartiallyCompatible {\n        access: BitFlags<T>,\n        incompatible: BitFlags<T>,\n    },\n}\n\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum RestrictSelfError {\n    /// The `prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)` system call failed.\n    #[error(\"failed to set no_new_privs: {source}\")]\n    #[non_exhaustive]\n    SetNoNewPrivsCall { source: io::Error },\n    /// The `landlock_restrict_self() `system call failed.\n    #[error(\"failed to restrict the calling thread: {source}\")]\n    #[non_exhaustive]\n    RestrictSelfCall { source: io::Error },\n}\n\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum PathFdError {\n    /// The `open()` system call failed.\n    #[error(\"failed to open \\\"{path}\\\": {source}\")]\n    #[non_exhaustive]\n    OpenCall { source: io::Error, path: PathBuf },\n}\n\n#[cfg(test)]\n#[derive(Debug, Error)]\npub(crate) enum TestRulesetError {\n    #[error(transparent)]\n    Ruleset(#[from] RulesetError),\n    #[error(transparent)]\n    PathFd(#[from] PathFdError),\n    #[error(transparent)]\n    File(#[from] std::io::Error),\n}\n\n/// Get the underlying errno value.\n///\n/// This helper is useful for FFI to easily translate a Landlock error into an\n/// errno value.\n#[derive(Debug, PartialEq, Eq)]\npub struct Errno(c_int);\n\nimpl Errno {\n    pub fn new(value: c_int) -> Self {\n        Self(value)\n    }\n}\n\nimpl<T> From<T> for Errno\nwhere\n    T: std::error::Error,\n{\n    fn from(error: T) -> Self {\n        let default = libc::EINVAL;\n        if let Some(e) = error.source() {\n            if let Some(e) = e.downcast_ref::<std::io::Error>() {\n                return Errno(e.raw_os_error().unwrap_or(default));\n            }\n        }\n        Errno(default)\n    }\n}\n\nimpl std::ops::Deref for Errno {\n    type Target = c_int;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\n#[cfg(test)]\nfn _test_ruleset_errno(expected_errno: c_int) {\n    use std::io::Error;\n\n    let handle_access_err = RulesetError::HandleAccesses(HandleAccessesError::Fs(\n        HandleAccessError::Compat(CompatError::Access(AccessError::Empty)),\n    ));\n    assert_eq!(*Errno::from(handle_access_err), libc::EINVAL);\n\n    let create_ruleset_err = RulesetError::CreateRuleset(CreateRulesetError::CreateRulesetCall {\n        source: Error::from_raw_os_error(expected_errno),\n    });\n    assert_eq!(*Errno::from(create_ruleset_err), expected_errno);\n\n    let add_rules_fs_err = RulesetError::AddRules(AddRulesError::Fs(AddRuleError::AddRuleCall {\n        source: Error::from_raw_os_error(expected_errno),\n    }));\n    assert_eq!(*Errno::from(add_rules_fs_err), expected_errno);\n\n    let add_rules_net_err = RulesetError::AddRules(AddRulesError::Net(AddRuleError::AddRuleCall {\n        source: Error::from_raw_os_error(expected_errno),\n    }));\n    assert_eq!(*Errno::from(add_rules_net_err), expected_errno);\n\n    let add_rules_other_err =\n        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess {\n            access: AccessFs::Execute.into(),\n            incompatible: BitFlags::<AccessFs>::EMPTY,\n        }));\n    assert_eq!(*Errno::from(add_rules_other_err), libc::EINVAL);\n\n    let restrict_self_err = RulesetError::RestrictSelf(RestrictSelfError::RestrictSelfCall {\n        source: Error::from_raw_os_error(expected_errno),\n    });\n    assert_eq!(*Errno::from(restrict_self_err), expected_errno);\n\n    let set_no_new_privs_err = RulesetError::RestrictSelf(RestrictSelfError::SetNoNewPrivsCall {\n        source: Error::from_raw_os_error(expected_errno),\n    });\n    assert_eq!(*Errno::from(set_no_new_privs_err), expected_errno);\n\n    let create_ruleset_missing_err =\n        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess);\n    assert_eq!(*Errno::from(create_ruleset_missing_err), libc::EINVAL);\n}\n\n#[test]\nfn test_ruleset_errno() {\n    _test_ruleset_errno(libc::EACCES);\n    _test_ruleset_errno(libc::EIO);\n}\n"
  },
  {
    "path": "src/fs.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::compat::private::OptionCompatLevelMut;\nuse crate::{\n    uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState,\n    Compatible, HandleAccessError, HandleAccessesError, HandledAccess, PathBeneathError,\n    PathFdError, PrivateHandledAccess, PrivateRule, Rule, Ruleset, RulesetCreated, RulesetError,\n    TailoredCompatLevel, TryCompat, ABI,\n};\nuse enumflags2::{bitflags, make_bitflags, BitFlags};\nuse std::fs::OpenOptions;\nuse std::io::Error;\nuse std::mem::zeroed;\nuse std::os::unix::fs::OpenOptionsExt;\nuse std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd};\nuse std::path::Path;\n\n#[cfg(test)]\nuse crate::{RulesetAttr, RulesetCreatedAttr};\n#[cfg(test)]\nuse strum::IntoEnumIterator;\n\n/// File system access right.\n///\n/// Each variant of `AccessFs` is an [access right](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights)\n/// for the file system.\n/// A set of access rights can be created with [`BitFlags<AccessFs>`](BitFlags).\n///\n/// # Example\n///\n/// ```\n/// use landlock::{ABI, Access, AccessFs, BitFlags, make_bitflags};\n///\n/// let exec = AccessFs::Execute;\n///\n/// let exec_set: BitFlags<AccessFs> = exec.into();\n///\n/// let file_content = make_bitflags!(AccessFs::{Execute | WriteFile | ReadFile});\n///\n/// let fs_v1 = AccessFs::from_all(ABI::V1);\n///\n/// let without_exec = fs_v1 & !AccessFs::Execute;\n///\n/// assert_eq!(fs_v1 | AccessFs::Refer, AccessFs::from_all(ABI::V2));\n/// ```\n///\n/// # Warning\n///\n/// To avoid unknown restrictions **don't use `BitFlags::<AccessFs>::all()` nor `BitFlags::ALL`**,\n/// but use a version you tested and vetted instead,\n/// for instance [`AccessFs::from_all(ABI::V1)`](Access::from_all).\n/// Direct use of **the [`BitFlags`] API is deprecated**.\n/// See [`ABI`] for the rationale and help to test it.\n#[bitflags]\n#[repr(u64)]\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub enum AccessFs {\n    /// Execute a file.\n    Execute = uapi::LANDLOCK_ACCESS_FS_EXECUTE as u64,\n    /// Open a file with write access.\n    ///\n    /// # Note\n    ///\n    /// Certain operations (such as [`std::fs::write`]) may also require [`AccessFs::Truncate`] since [`ABI::V3`].\n    WriteFile = uapi::LANDLOCK_ACCESS_FS_WRITE_FILE as u64,\n    /// Open a file with read access.\n    ReadFile = uapi::LANDLOCK_ACCESS_FS_READ_FILE as u64,\n    /// Open a directory or list its content.\n    ReadDir = uapi::LANDLOCK_ACCESS_FS_READ_DIR as u64,\n    /// Remove an empty directory or rename one.\n    RemoveDir = uapi::LANDLOCK_ACCESS_FS_REMOVE_DIR as u64,\n    /// Unlink (or rename) a file.\n    RemoveFile = uapi::LANDLOCK_ACCESS_FS_REMOVE_FILE as u64,\n    /// Create (or rename or link) a character device.\n    MakeChar = uapi::LANDLOCK_ACCESS_FS_MAKE_CHAR as u64,\n    /// Create (or rename) a directory.\n    MakeDir = uapi::LANDLOCK_ACCESS_FS_MAKE_DIR as u64,\n    /// Create (or rename or link) a regular file.\n    MakeReg = uapi::LANDLOCK_ACCESS_FS_MAKE_REG as u64,\n    /// Create (or rename or link) a UNIX domain socket.\n    MakeSock = uapi::LANDLOCK_ACCESS_FS_MAKE_SOCK as u64,\n    /// Create (or rename or link) a named pipe.\n    MakeFifo = uapi::LANDLOCK_ACCESS_FS_MAKE_FIFO as u64,\n    /// Create (or rename or link) a block device.\n    MakeBlock = uapi::LANDLOCK_ACCESS_FS_MAKE_BLOCK as u64,\n    /// Create (or rename or link) a symbolic link.\n    MakeSym = uapi::LANDLOCK_ACCESS_FS_MAKE_SYM as u64,\n    /// Link or rename a file from or to a different directory.\n    Refer = uapi::LANDLOCK_ACCESS_FS_REFER as u64,\n    /// Truncate a file with `truncate(2)`, `ftruncate(2)`, `creat(2)`, or `open(2)` with `O_TRUNC`.\n    Truncate = uapi::LANDLOCK_ACCESS_FS_TRUNCATE as u64,\n    /// Send IOCL commands to a device file.\n    IoctlDev = uapi::LANDLOCK_ACCESS_FS_IOCTL_DEV as u64,\n}\n\nimpl Access for AccessFs {\n    /// Union of [`from_read()`](AccessFs::from_read) and [`from_write()`](AccessFs::from_write).\n    fn from_all(abi: ABI) -> BitFlags<Self> {\n        // An empty access-right would be an error if passed to the kernel, but because the kernel\n        // doesn't support Landlock, no Landlock syscall should be called.  try_compat() should\n        // also return RestrictionStatus::Unrestricted when called with unsupported/empty\n        // access-rights.\n        Self::from_read(abi) | Self::from_write(abi)\n    }\n}\n\nimpl AccessFs {\n    // Roughly read (i.e. not all FS actions are handled).\n    /// Gets the access rights identified as read-only according to a specific ABI.\n    /// Exclusive with [`from_write()`](AccessFs::from_write).\n    pub fn from_read(abi: ABI) -> BitFlags<Self> {\n        match abi {\n            ABI::Unsupported => BitFlags::EMPTY,\n            ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 | ABI::V6 => make_bitflags!(AccessFs::{\n                Execute\n                | ReadFile\n                | ReadDir\n            }),\n        }\n    }\n\n    // Roughly write (i.e. not all FS actions are handled).\n    /// Gets the access rights identified as write-only according to a specific ABI.\n    /// Exclusive with [`from_read()`](AccessFs::from_read).\n    pub fn from_write(abi: ABI) -> BitFlags<Self> {\n        match abi {\n            ABI::Unsupported => BitFlags::EMPTY,\n            ABI::V1 => make_bitflags!(AccessFs::{\n                WriteFile\n                | RemoveDir\n                | RemoveFile\n                | MakeChar\n                | MakeDir\n                | MakeReg\n                | MakeSock\n                | MakeFifo\n                | MakeBlock\n                | MakeSym\n            }),\n            ABI::V2 => Self::from_write(ABI::V1) | AccessFs::Refer,\n            ABI::V3 | ABI::V4 => Self::from_write(ABI::V2) | AccessFs::Truncate,\n            ABI::V5 | ABI::V6 => Self::from_write(ABI::V4) | AccessFs::IoctlDev,\n        }\n    }\n\n    /// Gets the access rights legitimate for non-directory files.\n    pub fn from_file(abi: ABI) -> BitFlags<Self> {\n        Self::from_all(abi) & ACCESS_FILE\n    }\n}\n\n#[test]\nfn consistent_access_fs_rw() {\n    for abi in ABI::iter() {\n        let access_all = AccessFs::from_all(abi);\n        let access_read = AccessFs::from_read(abi);\n        let access_write = AccessFs::from_write(abi);\n        let access_file = AccessFs::from_file(abi);\n        assert_eq!(access_read, !access_write & access_all);\n        assert_eq!(access_read | access_write, access_all);\n        assert_eq!(access_file, access_all & ACCESS_FILE);\n    }\n}\n\nimpl HandledAccess for AccessFs {}\n\nimpl PrivateHandledAccess for AccessFs {\n    fn ruleset_handle_access(\n        ruleset: &mut Ruleset,\n        access: BitFlags<Self>,\n    ) -> Result<(), HandleAccessesError> {\n        // We need to record the requested accesses for PrivateRule::check_consistency().\n        ruleset.requested_handled_fs |= access;\n        ruleset.actual_handled_fs |= match access\n            .try_compat(\n                ruleset.compat.abi(),\n                ruleset.compat.level,\n                &mut ruleset.compat.state,\n            )\n            .map_err(HandleAccessError::Compat)?\n        {\n            Some(a) => a,\n            None => return Ok(()),\n        };\n        Ok(())\n    }\n\n    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {\n        AddRulesError::Fs(error)\n    }\n\n    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {\n        HandleAccessesError::Fs(error)\n    }\n}\n\n// TODO: Make ACCESS_FILE a property of AccessFs.\n// TODO: Add tests for ACCESS_FILE.\nconst ACCESS_FILE: BitFlags<AccessFs> = make_bitflags!(AccessFs::{\n    ReadFile | WriteFile | Execute | Truncate | IoctlDev\n});\n\n// XXX: What should we do when a stat call failed?\nfn is_file<F>(fd: F) -> Result<bool, Error>\nwhere\n    F: AsFd,\n{\n    unsafe {\n        let mut stat = zeroed();\n        match libc::fstat(fd.as_fd().as_raw_fd(), &mut stat) {\n            0 => Ok((stat.st_mode & libc::S_IFMT) != libc::S_IFDIR),\n            _ => Err(Error::last_os_error()),\n        }\n    }\n}\n\n/// Landlock rule for a file hierarchy.\n///\n/// # Example\n///\n/// ```\n/// use landlock::{AccessFs, PathBeneath, PathFd, PathFdError};\n///\n/// fn home_dir() -> Result<PathBeneath<PathFd>, PathFdError> {\n///     Ok(PathBeneath::new(PathFd::new(\"/home\")?, AccessFs::ReadDir))\n/// }\n/// ```\n#[derive(Debug)]\npub struct PathBeneath<F> {\n    attr: uapi::landlock_path_beneath_attr,\n    // Ties the lifetime of a file descriptor to this object.\n    parent_fd: F,\n    allowed_access: BitFlags<AccessFs>,\n    compat_level: Option<CompatLevel>,\n}\n\nimpl<F> PathBeneath<F>\nwhere\n    F: AsFd,\n{\n    /// Creates a new `PathBeneath` rule identifying the `parent` directory of a file hierarchy,\n    /// or just a file, and allows `access` on it.\n    /// The `parent` file descriptor will be automatically closed with the returned `PathBeneath`.\n    pub fn new<A>(parent: F, access: A) -> Self\n    where\n        A: Into<BitFlags<AccessFs>>,\n    {\n        PathBeneath {\n            // Invalid access rights until as_ptr() is called.\n            attr: unsafe { zeroed() },\n            parent_fd: parent,\n            allowed_access: access.into(),\n            compat_level: None,\n        }\n    }\n}\n\nimpl<F> TryCompat<AccessFs> for PathBeneath<F>\nwhere\n    F: AsFd,\n{\n    fn try_compat_children<L>(\n        mut self,\n        abi: ABI,\n        parent_level: L,\n        compat_state: &mut CompatState,\n    ) -> Result<Option<Self>, CompatError<AccessFs>>\n    where\n        L: Into<CompatLevel>,\n    {\n        // Checks with our own compatibility level, if any.\n        self.allowed_access = match self.allowed_access.try_compat(\n            abi,\n            self.tailored_compat_level(parent_level),\n            compat_state,\n        )? {\n            Some(a) => a,\n            None => return Ok(None),\n        };\n        Ok(Some(self))\n    }\n\n    fn try_compat_inner(\n        &mut self,\n        _abi: ABI,\n    ) -> Result<CompatResult<AccessFs>, CompatError<AccessFs>> {\n        // Gets subset of valid accesses according the FD type.\n        let valid_access =\n            if is_file(&self.parent_fd).map_err(|e| PathBeneathError::StatCall { source: e })? {\n                self.allowed_access & ACCESS_FILE\n            } else {\n                self.allowed_access\n            };\n\n        if self.allowed_access != valid_access {\n            let error = PathBeneathError::DirectoryAccess {\n                access: self.allowed_access,\n                incompatible: self.allowed_access ^ valid_access,\n            }\n            .into();\n            self.allowed_access = valid_access;\n            // Linux would return EINVAL.\n            Ok(CompatResult::Partial(error))\n        } else {\n            Ok(CompatResult::Full)\n        }\n    }\n}\n\n#[test]\nfn path_beneath_try_compat_children() {\n    use crate::*;\n\n    // AccessFs::Refer is not handled by ABI::V1 and only for directories.\n    let access_file = AccessFs::ReadFile | AccessFs::Refer;\n\n    // Test error ordering with ABI::V1\n    let mut ruleset = Ruleset::from(ABI::V1).handle_access(access_file).unwrap();\n    // Do not actually perform any syscall.\n    ruleset.compat.state = CompatState::Dummy;\n    assert!(matches!(\n        RulesetCreated::new(ruleset, None)\n            .set_compatibility(CompatLevel::HardRequirement)\n            .add_rule(PathBeneath::new(PathFd::new(\"/dev/null\").unwrap(), access_file))\n            .unwrap_err(),\n        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::Compat(\n            CompatError::PathBeneath(PathBeneathError::DirectoryAccess { access, incompatible })\n        ))) if access == access_file && incompatible == AccessFs::Refer\n    ));\n\n    // Test error ordering with ABI::V2\n    let mut ruleset = Ruleset::from(ABI::V2).handle_access(access_file).unwrap();\n    // Do not actually perform any syscall.\n    ruleset.compat.state = CompatState::Dummy;\n    assert!(matches!(\n        RulesetCreated::new(ruleset, None)\n            .set_compatibility(CompatLevel::HardRequirement)\n            .add_rule(PathBeneath::new(PathFd::new(\"/dev/null\").unwrap(), access_file))\n            .unwrap_err(),\n        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::Compat(\n            CompatError::PathBeneath(PathBeneathError::DirectoryAccess { access, incompatible })\n        ))) if access == access_file && incompatible == AccessFs::Refer\n    ));\n}\n\n#[test]\nfn path_beneath_try_compat() {\n    use crate::*;\n\n    let abi = ABI::V1;\n\n    for file in &[\"/etc/passwd\", \"/dev/null\"] {\n        let mut compat_state = CompatState::Init;\n        let ro_access = AccessFs::ReadDir | AccessFs::ReadFile;\n        assert!(matches!(\n            PathBeneath::new(PathFd::new(file).unwrap(), ro_access)\n                .try_compat(abi, CompatLevel::HardRequirement, &mut compat_state)\n                .unwrap_err(),\n            CompatError::PathBeneath(PathBeneathError::DirectoryAccess { access, incompatible })\n                if access == ro_access && incompatible == AccessFs::ReadDir\n        ));\n\n        let mut compat_state = CompatState::Init;\n        assert!(matches!(\n            PathBeneath::new(PathFd::new(file).unwrap(), BitFlags::EMPTY)\n                .try_compat(abi, CompatLevel::BestEffort, &mut compat_state)\n                .unwrap_err(),\n            CompatError::Access(AccessError::Empty)\n        ));\n    }\n\n    let full_access = AccessFs::from_all(ABI::V1);\n    for compat_level in &[\n        CompatLevel::BestEffort,\n        CompatLevel::SoftRequirement,\n        CompatLevel::HardRequirement,\n    ] {\n        let mut compat_state = CompatState::Init;\n        let mut path_beneath = PathBeneath::new(PathFd::new(\"/\").unwrap(), full_access)\n            .try_compat(abi, *compat_level, &mut compat_state)\n            .unwrap()\n            .unwrap();\n        assert_eq!(compat_state, CompatState::Full);\n\n        // Without synchronization.\n        let raw_access = path_beneath.attr.allowed_access;\n        assert_eq!(raw_access, 0);\n\n        // Synchronize the inner attribute buffer.\n        let _ = path_beneath.as_ptr();\n        let raw_access = path_beneath.attr.allowed_access;\n        assert_eq!(raw_access, full_access.bits());\n    }\n}\n\nimpl<F> OptionCompatLevelMut for PathBeneath<F> {\n    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {\n        &mut self.compat_level\n    }\n}\n\nimpl<F> OptionCompatLevelMut for &mut PathBeneath<F> {\n    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {\n        &mut self.compat_level\n    }\n}\n\nimpl<F> Compatible for PathBeneath<F> {}\n\nimpl<F> Compatible for &mut PathBeneath<F> {}\n\n#[test]\nfn path_beneath_compatibility() {\n    let mut path = PathBeneath::new(PathFd::new(\"/\").unwrap(), AccessFs::from_all(ABI::V1));\n    let path_ref = &mut path;\n\n    let level = path_ref.as_option_compat_level_mut();\n    assert_eq!(level, &None);\n    assert_eq!(\n        <Option<CompatLevel> as Into<CompatLevel>>::into(*level),\n        CompatLevel::BestEffort\n    );\n\n    path_ref.set_compatibility(CompatLevel::SoftRequirement);\n    assert_eq!(\n        path_ref.as_option_compat_level_mut(),\n        &Some(CompatLevel::SoftRequirement)\n    );\n\n    path.set_compatibility(CompatLevel::HardRequirement);\n}\n\n// It is useful for documentation generation to explicitely implement Rule for every types, instead\n// of doing it generically.\nimpl<F> Rule<AccessFs> for PathBeneath<F> where F: AsFd {}\n\nimpl<F> PrivateRule<AccessFs> for PathBeneath<F>\nwhere\n    F: AsFd,\n{\n    const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH;\n\n    fn as_ptr(&mut self) -> *const libc::c_void {\n        self.attr.parent_fd = self.parent_fd.as_fd().as_raw_fd();\n        self.attr.allowed_access = self.allowed_access.bits();\n        &self.attr as *const _ as _\n    }\n\n    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {\n        // Checks that this rule doesn't contain a superset of the access-rights handled by the\n        // ruleset.  This check is about requested access-rights but not actual access-rights.\n        // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel\n        // (which is handled by Ruleset and RulesetCreated).\n        if ruleset.requested_handled_fs.contains(self.allowed_access) {\n            Ok(())\n        } else {\n            Err(AddRuleError::UnhandledAccess {\n                access: self.allowed_access,\n                incompatible: self.allowed_access & !ruleset.requested_handled_fs,\n            }\n            .into())\n        }\n    }\n}\n\n#[test]\nfn path_beneath_check_consistency() {\n    use crate::*;\n\n    let ro_access = AccessFs::ReadDir | AccessFs::ReadFile;\n    let rx_access = AccessFs::Execute | AccessFs::ReadFile;\n    assert!(matches!(\n        Ruleset::from(ABI::Unsupported)\n            .handle_access(ro_access)\n            .unwrap()\n            .create()\n            .unwrap()\n            .add_rule(PathBeneath::new(PathFd::new(\"/\").unwrap(), rx_access))\n            .unwrap_err(),\n        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { access, incompatible }))\n            if access == rx_access && incompatible == AccessFs::Execute\n    ));\n}\n\n/// Simple helper to open a file or a directory with the `O_PATH` flag.\n///\n/// This is the recommended way to identify a path\n/// and manage the lifetime of the underlying opened file descriptor.\n/// Indeed, using other [`AsFd`] implementations such as [`File`] brings more complexity\n/// and may lead to unexpected errors (e.g., denied access).\n///\n/// [`File`]: std::fs::File\n///\n/// # Example\n///\n/// ```\n/// use landlock::{AccessFs, PathBeneath, PathFd, PathFdError};\n///\n/// fn allowed_root_dir(access: AccessFs) -> Result<PathBeneath<PathFd>, PathFdError> {\n///     let fd = PathFd::new(\"/\")?;\n///     Ok(PathBeneath::new(fd, access))\n/// }\n/// ```\n#[derive(Debug)]\npub struct PathFd {\n    fd: OwnedFd,\n}\n\nimpl PathFd {\n    pub fn new<T>(path: T) -> Result<Self, PathFdError>\n    where\n        T: AsRef<Path>,\n    {\n        Ok(PathFd {\n            fd: OpenOptions::new()\n                .read(true)\n                // If the O_PATH is not supported, it is automatically ignored (Linux < 2.6.39).\n                .custom_flags(libc::O_PATH | libc::O_CLOEXEC)\n                .open(path.as_ref())\n                .map_err(|e| PathFdError::OpenCall {\n                    source: e,\n                    path: path.as_ref().into(),\n                })?\n                .into(),\n        })\n    }\n}\n\nimpl AsFd for PathFd {\n    fn as_fd(&self) -> BorrowedFd<'_> {\n        self.fd.as_fd()\n    }\n}\n\n#[test]\nfn path_fd() {\n    use std::fs::File;\n    use std::io::Read;\n\n    PathBeneath::new(PathFd::new(\"/\").unwrap(), AccessFs::Execute);\n    PathBeneath::new(File::open(\"/\").unwrap(), AccessFs::Execute);\n\n    let mut buffer = [0; 1];\n    // Checks that PathFd really returns an FD opened with O_PATH (Bad file descriptor error).\n    File::from(PathFd::new(\"/etc/passwd\").unwrap().fd)\n        .read(&mut buffer)\n        .unwrap_err();\n}\n\n/// Helper to quickly create an iterator of PathBeneath rules.\n///\n/// # Note\n///\n/// From the kernel's perspective, Landlock rules operate on file descriptors, not paths.\n/// This is a helper to create rules based on paths. Here, `path_beneath_rules()` silently ignores\n/// paths that cannot be opened, hence making the obtainment of a file descriptor impossible. When\n/// possible and for a given path, `path_beneath_rules()` automatically adjusts [access rights](`AccessFs`),\n/// depending on whether a directory or a file is present at that said path.\n///\n/// This behavior is the result of [`CompatLevel::BestEffort`], which is the default compatibility level of\n/// all created rulesets. Thus, it applies to the example below. However, if [`CompatLevel::HardRequirement`]\n/// is set using [`Compatible::set_compatibility`], attempting to create an incompatible rule at runtime will cause\n/// this crate to raise an error instead.\n///\n/// # Example\n///\n/// ```\n/// use landlock::{\n///     ABI, Access, AccessFs, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus, RulesetError,\n///     path_beneath_rules,\n/// };\n///\n/// fn restrict_thread() -> Result<(), RulesetError> {\n///     let abi = ABI::V1;\n///     let status = Ruleset::default()\n///         .handle_access(AccessFs::from_all(abi))?\n///         .create()?\n///         // Read-only access to /usr, /etc and /dev.\n///         .add_rules(path_beneath_rules(&[\"/usr\", \"/etc\", \"/dev\"], AccessFs::from_read(abi)))?\n///         // Read-write access to /home and /tmp.\n///         .add_rules(path_beneath_rules(&[\"/home\", \"/tmp\"], AccessFs::from_all(abi)))?\n///         .restrict_self()?;\n///     match status.ruleset {\n///         // The FullyEnforced case must be tested by the developer.\n///         RulesetStatus::FullyEnforced => println!(\"Fully sandboxed.\"),\n///         RulesetStatus::PartiallyEnforced => println!(\"Partially sandboxed.\"),\n///         // Users should be warned that they are not protected.\n///         RulesetStatus::NotEnforced => println!(\"Not sandboxed! Please update your kernel.\"),\n///     }\n///     Ok(())\n/// }\n/// ```\npub fn path_beneath_rules<I, P, A>(\n    paths: I,\n    access: A,\n) -> impl Iterator<Item = Result<PathBeneath<PathFd>, RulesetError>>\nwhere\n    I: IntoIterator<Item = P>,\n    P: AsRef<Path>,\n    A: Into<BitFlags<AccessFs>>,\n{\n    let access = access.into();\n    paths.into_iter().filter_map(move |p| match PathFd::new(p) {\n        Ok(f) => {\n            let valid_access = match is_file(&f) {\n                Ok(true) => access & ACCESS_FILE,\n                // If the stat call failed, let's blindly rely on the requested access rights.\n                Err(_) | Ok(false) => access,\n            };\n            Some(Ok(PathBeneath::new(f, valid_access)))\n        }\n        Err(_) => None,\n    })\n}\n\n#[test]\nfn path_beneath_rules_iter() {\n    let _ = Ruleset::default()\n        .handle_access(AccessFs::from_all(ABI::V1))\n        .unwrap()\n        .create()\n        .unwrap()\n        .add_rules(path_beneath_rules(\n            &[\"/usr\", \"/opt\", \"/does-not-exist\", \"/root\"],\n            AccessFs::Execute,\n        ))\n        .unwrap();\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n//! Landlock is a security feature available since Linux 5.13.\n//! The goal is to enable to restrict ambient rights\n//! (e.g., global filesystem access)\n//! for a set of processes by creating safe security sandboxes as new security layers\n//! in addition to the existing system-wide access-controls.\n//! This kind of sandbox is expected to help mitigate the security impact of bugs,\n//! unexpected or malicious behaviors in applications.\n//! Landlock empowers any process, including unprivileged ones, to securely restrict themselves.\n//! More information about Landlock can be found in the [official website](https://landlock.io).\n//!\n//! This crate provides a safe abstraction for the Landlock system calls, along with some helpers.\n//!\n//! Minimum Supported Rust Version (MSRV): 1.71\n//!\n//! # Use cases\n//!\n//! This crate is especially useful to protect users' data by sandboxing:\n//! * trusted applications dealing with potentially malicious data\n//!   (e.g., complex file format, network request) that could exploit security vulnerabilities;\n//! * sandbox managers, container runtimes or shells launching untrusted applications.\n//!\n//! # Examples\n//!\n//! A simple example can be found with the [`path_beneath_rules()`] helper.\n//! More complex examples can be found with the [`Ruleset` documentation](Ruleset)\n//! and the [sandboxer example](https://github.com/landlock-lsm/rust-landlock/blob/master/examples/sandboxer.rs).\n//!\n//! # Current limitations\n//!\n//! This crate exposes the Landlock features available as of Linux 5.19\n//! and then inherits some [kernel limitations](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#current-limitations)\n//! that will be addressed with future kernel releases\n//! (e.g., arbitrary mounts are always denied).\n//!\n//! # Compatibility\n//!\n//! Types defined in this crate are designed to enable the strictest Landlock configuration\n//! for the given kernel on which the program runs.\n//! In the default [best-effort](CompatLevel::BestEffort) mode,\n//! [`Ruleset`] will determine compatibility\n//! with the intersection of the currently running kernel's features\n//! and those required by the caller.\n//! This way, callers can distinguish between\n//! Landlock compatibility issues inherent to the current system\n//! (e.g., file names that don't exist)\n//! and misconfiguration that should be fixed in the program\n//! (e.g., empty or inconsistent access rights).\n//! [`RulesetError`] identifies such kind of errors.\n//!\n//! With [`set_compatibility(CompatLevel::BestEffort)`](Compatible::set_compatibility),\n//! users of the crate may mark Landlock features that are deemed required\n//! and other features that may be downgraded to use lower security on systems\n//! where they can't be enforced.\n//! It is discouraged to compare the system's provided [Landlock ABI](ABI) version directly,\n//! as it is difficult to track detailed ABI differences\n//! which are handled thanks to the [`Compatible`] trait.\n//!\n//! To make it easier to migrate to a new version of this library,\n//! we use the builder pattern\n//! and designed objects to require the minimal set of method arguments.\n//! Most `enum` are marked as `non_exhaustive` to enable backward-compatible evolutions.\n//!\n//! ## Test strategy\n//!\n//! Developers should test their sandboxed applications\n//! with a kernel that supports all requested Landlock features\n//! and check that [`RulesetCreated::restrict_self()`] returns a status matching\n//! [`Ok(RestrictionStatus { ruleset: RulesetStatus::FullyEnforced, no_new_privs: true, })`](RestrictionStatus)\n//! to make sure everything works as expected in an enforced sandbox.\n//! Alternatively, using [`set_compatibility(CompatLevel::HardRequirement)`](Compatible::set_compatibility)\n//! will immediately inform about unsupported Landlock features.\n//! These configurations should only depend on the test environment\n//! (e.g. [by checking an environment variable](https://github.com/landlock-lsm/rust-landlock/search?q=LANDLOCK_CRATE_TEST_ABI)).\n//! However, applications should only check that no error is returned (i.e. `Ok(_)`)\n//! and optionally log and inform users that the application is not fully sandboxed\n//! because of missing features from the running kernel.\n\n#[cfg(test)]\n#[macro_use]\nextern crate lazy_static;\n\npub use access::{Access, HandledAccess};\npub use compat::{CompatLevel, Compatible, LandlockStatus, ABI};\npub use enumflags2::{make_bitflags, BitFlags};\npub use errata::Erratum;\npub use errors::{\n    AccessError, AddRuleError, AddRulesError, CompatError, CreateRulesetError, Errno,\n    HandleAccessError, HandleAccessesError, PathBeneathError, PathFdError, RestrictSelfError,\n    RulesetError, ScopeError,\n};\npub use fs::{path_beneath_rules, AccessFs, PathBeneath, PathFd};\npub use net::{AccessNet, NetPort};\npub use ruleset::{\n    RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr,\n    RulesetStatus,\n};\npub use scope::Scope;\n\nuse access::PrivateHandledAccess;\nuse compat::{CompatResult, CompatState, Compatibility, TailoredCompatLevel, TryCompat};\nuse ruleset::PrivateRule;\n\n#[cfg(test)]\nuse compat::{can_emulate, get_errno_from_landlock_status};\n#[cfg(test)]\nuse errors::TestRulesetError;\n#[cfg(test)]\nuse strum::IntoEnumIterator;\n\nmod access;\nmod compat;\nmod errata;\nmod errors;\nmod fs;\nmod net;\nmod ruleset;\nmod scope;\nmod uapi;\n\n// Makes sure private traits cannot be implemented outside of this crate.\nmod private {\n    pub trait Sealed {}\n\n    impl Sealed for crate::AccessFs {}\n    impl Sealed for crate::AccessNet {}\n    impl Sealed for crate::Scope {}\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::*;\n\n    // Emulate old kernel supports.\n    fn check_ruleset_support<F>(\n        partial: ABI,\n        full: Option<ABI>,\n        check: F,\n        error_if_abi_lt_partial: bool,\n    ) where\n        F: Fn(Ruleset) -> Result<RestrictionStatus, TestRulesetError> + Send + Copy + 'static,\n    {\n        // If there is no partial support, it means that `full == partial`.\n        assert!(partial <= full.unwrap_or(partial));\n        for abi in ABI::iter() {\n            // Ensures restrict_self() is called on a dedicated thread to avoid inconsistent tests.\n            let ret = std::thread::spawn(move || check(Ruleset::from(abi)))\n                .join()\n                .unwrap();\n\n            // Useful for failed tests and with cargo test -- --show-output\n            println!(\"Checking ABI {abi:?}: received {ret:#?}\");\n            if can_emulate(abi, partial, full) {\n                if abi < partial && error_if_abi_lt_partial {\n                    // TODO: Check exact error type; this may require better error types.\n                    assert!(matches!(ret, Err(TestRulesetError::Ruleset(_))));\n                } else {\n                    let full_support = if let Some(full_inner) = full {\n                        abi >= full_inner\n                    } else {\n                        false\n                    };\n                    let ruleset_status = if full_support {\n                        RulesetStatus::FullyEnforced\n                    } else if abi >= partial {\n                        RulesetStatus::PartiallyEnforced\n                    } else {\n                        RulesetStatus::NotEnforced\n                    };\n                    let landlock_status = abi.into();\n                    println!(\"Expecting ruleset status {ruleset_status:?}\");\n                    println!(\"Expecting Landlock status {landlock_status:?}\");\n                    assert!(matches!(\n                        ret,\n                        Ok(RestrictionStatus {\n                            ruleset,\n                            landlock,\n                            no_new_privs: true,\n                        }) if ruleset == ruleset_status && landlock == landlock_status\n                    ))\n                }\n            } else {\n                // The errno value should be ENOSYS, EOPNOTSUPP, EINVAL (e.g. when an unknown\n                // access right is provided), or E2BIG (e.g. when there is an unknown field in a\n                // Landlock syscall attribute).\n                let errno = get_errno_from_landlock_status();\n                println!(\"Expecting error {errno:?}\");\n                match ret {\n                    Err(\n                        ref error @ TestRulesetError::Ruleset(RulesetError::CreateRuleset(\n                            CreateRulesetError::CreateRulesetCall { ref source },\n                        )),\n                    ) => {\n                        assert_eq!(source.raw_os_error(), Some(*Errno::from(error)));\n                        match (source.raw_os_error(), errno) {\n                            (Some(e1), Some(e2)) => assert_eq!(e1, e2),\n                            (Some(e1), None) => assert!(matches!(e1, libc::EINVAL | libc::E2BIG)),\n                            _ => unreachable!(),\n                        }\n                    }\n                    _ => unreachable!(),\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn allow_root_compat() {\n        let abi = ABI::V1;\n\n        check_ruleset_support(\n            abi,\n            Some(abi),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::from_all(abi))?\n                    .create()?\n                    .add_rule(PathBeneath::new(PathFd::new(\"/\")?, AccessFs::from_all(abi)))?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn too_much_access_rights_for_a_file() {\n        let abi = ABI::V1;\n\n        check_ruleset_support(\n            abi,\n            Some(abi),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::from_all(abi))?\n                    .create()?\n                    // Same code as allow_root_compat() but with /etc/passwd instead of /\n                    .add_rule(PathBeneath::new(\n                        PathFd::new(\"/etc/passwd\")?,\n                        // Only allow legitimate access rights on a file.\n                        AccessFs::from_file(abi),\n                    ))?\n                    .restrict_self()?)\n            },\n            false,\n        );\n\n        check_ruleset_support(\n            abi,\n            None,\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::from_all(abi))?\n                    .create()?\n                    // Same code as allow_root_compat() but with /etc/passwd instead of /\n                    .add_rule(PathBeneath::new(\n                        PathFd::new(\"/etc/passwd\")?,\n                        // Tries to allow all access rights on a file.\n                        AccessFs::from_all(abi),\n                    ))?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn path_beneath_rules_with_too_much_access_rights_for_a_file() {\n        let abi = ABI::V1;\n\n        check_ruleset_support(\n            abi,\n            Some(abi),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::from_all(ABI::V1))?\n                    .create()?\n                    // Same code as too_much_access_rights_for_a_file() but using path_beneath_rules()\n                    .add_rules(path_beneath_rules([\"/etc/passwd\"], AccessFs::from_all(abi)))?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn allow_root_fragile() {\n        let abi = ABI::V1;\n\n        check_ruleset_support(\n            abi,\n            Some(abi),\n            move |ruleset: Ruleset| -> _ {\n                // Sets default support requirement: abort the whole sandboxing for any Landlock error.\n                Ok(ruleset\n                    // Must have at least the execute check…\n                    .set_compatibility(CompatLevel::HardRequirement)\n                    .handle_access(AccessFs::Execute)?\n                    // …and possibly others.\n                    .set_compatibility(CompatLevel::BestEffort)\n                    .handle_access(AccessFs::from_all(abi))?\n                    .create()?\n                    .set_no_new_privs(true)\n                    .add_rule(PathBeneath::new(PathFd::new(\"/\")?, AccessFs::from_all(abi)))?\n                    .restrict_self()?)\n            },\n            true,\n        );\n    }\n\n    #[test]\n    fn ruleset_enforced() {\n        let abi = ABI::V1;\n\n        check_ruleset_support(\n            abi,\n            Some(abi),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    // Restricting without rule exceptions is legitimate to forbid a set of actions.\n                    .handle_access(AccessFs::Execute)?\n                    .create()?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn abi_v2_exec_refer() {\n        check_ruleset_support(\n            ABI::V1,\n            Some(ABI::V2),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::Execute)?\n                    // AccessFs::Refer is not supported by ABI::V1 (best-effort).\n                    .handle_access(AccessFs::Refer)?\n                    .create()?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn abi_v2_refer_only() {\n        // When no access is handled, do not try to create a ruleset without access.\n        check_ruleset_support(\n            ABI::V2,\n            Some(ABI::V2),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::Refer)?\n                    .create()?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn abi_v3_truncate() {\n        check_ruleset_support(\n            ABI::V2,\n            Some(ABI::V3),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::Refer)?\n                    .handle_access(AccessFs::Truncate)?\n                    .create()?\n                    .add_rule(PathBeneath::new(PathFd::new(\"/\")?, AccessFs::Refer))?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn ruleset_created_try_clone() {\n        check_ruleset_support(\n            ABI::V1,\n            Some(ABI::V1),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::Execute)?\n                    .create()?\n                    .add_rule(PathBeneath::new(PathFd::new(\"/\")?, AccessFs::Execute))?\n                    .try_clone()?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn abi_v4_tcp() {\n        check_ruleset_support(\n            ABI::V3,\n            Some(ABI::V4),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::Truncate)?\n                    .handle_access(AccessNet::BindTcp | AccessNet::ConnectTcp)?\n                    .create()?\n                    .add_rule(NetPort::new(1, AccessNet::ConnectTcp))?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn abi_v5_ioctl_dev() {\n        check_ruleset_support(\n            ABI::V4,\n            Some(ABI::V5),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessNet::BindTcp)?\n                    .handle_access(AccessFs::IoctlDev)?\n                    .create()?\n                    .add_rule(PathBeneath::new(PathFd::new(\"/\")?, AccessFs::IoctlDev))?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn abi_v6_scope_mix() {\n        check_ruleset_support(\n            ABI::V5,\n            Some(ABI::V6),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .handle_access(AccessFs::IoctlDev)?\n                    .scope(Scope::AbstractUnixSocket | Scope::Signal)?\n                    .create()?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn abi_v6_scope_only() {\n        check_ruleset_support(\n            ABI::V6,\n            Some(ABI::V6),\n            move |ruleset: Ruleset| -> _ {\n                Ok(ruleset\n                    .scope(Scope::AbstractUnixSocket | Scope::Signal)?\n                    .create()?\n                    .restrict_self()?)\n            },\n            false,\n        );\n    }\n\n    #[test]\n    fn ruleset_created_try_clone_ownedfd() {\n        use std::os::unix::io::{AsRawFd, OwnedFd};\n\n        let abi = ABI::V1;\n        check_ruleset_support(\n            abi,\n            Some(abi),\n            move |ruleset: Ruleset| -> _ {\n                let ruleset1 = ruleset.handle_access(AccessFs::from_all(abi))?.create()?;\n                let ruleset2 = ruleset1.try_clone().unwrap();\n                let ruleset3 = ruleset2.try_clone().unwrap();\n\n                let some1: Option<OwnedFd> = ruleset1.into();\n                if let Some(fd1) = some1 {\n                    assert!(fd1.as_raw_fd() >= 0);\n\n                    let some2: Option<OwnedFd> = ruleset2.into();\n                    let fd2 = some2.unwrap();\n                    assert!(fd2.as_raw_fd() >= 0);\n\n                    assert_ne!(fd1.as_raw_fd(), fd2.as_raw_fd());\n                }\n                Ok(ruleset3.restrict_self()?)\n            },\n            false,\n        );\n    }\n}\n"
  },
  {
    "path": "src/net.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::compat::private::OptionCompatLevelMut;\nuse crate::{\n    uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState,\n    Compatible, HandleAccessError, HandleAccessesError, HandledAccess, PrivateHandledAccess,\n    PrivateRule, Rule, Ruleset, RulesetCreated, TailoredCompatLevel, TryCompat, ABI,\n};\nuse enumflags2::{bitflags, BitFlags};\nuse std::mem::zeroed;\n\n/// Network access right.\n///\n/// Each variant of `AccessNet` is an [access right](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights)\n/// for the network.\n/// A set of access rights can be created with [`BitFlags<AccessNet>`](BitFlags).\n///\n/// # Example\n///\n/// ```\n/// use landlock::{ABI, Access, AccessNet, BitFlags, make_bitflags};\n///\n/// let bind = AccessNet::BindTcp;\n///\n/// let bind_set: BitFlags<AccessNet> = bind.into();\n///\n/// let bind_connect = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});\n///\n/// let net_v4 = AccessNet::from_all(ABI::V4);\n///\n/// assert_eq!(bind_connect, net_v4);\n/// ```\n///\n/// # Warning\n///\n/// To avoid unknown restrictions **don't use `BitFlags::<AccessNet>::all()` nor `BitFlags::ALL`**,\n/// but use a version you tested and vetted instead,\n/// for instance [`AccessNet::from_all(ABI::V4)`](Access::from_all).\n/// Direct use of **the [`BitFlags`] API is deprecated**.\n/// See [`ABI`] for the rationale and help to test it.\n#[bitflags]\n#[repr(u64)]\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub enum AccessNet {\n    /// Bind to a TCP port.\n    BindTcp = uapi::LANDLOCK_ACCESS_NET_BIND_TCP as u64,\n    /// Connect to a TCP port.\n    ConnectTcp = uapi::LANDLOCK_ACCESS_NET_CONNECT_TCP as u64,\n}\n\n/// # Warning\n///\n/// If `ABI <= ABI::V3`, `AccessNet::from_all()` returns an empty `BitFlags<AccessNet>`, which\n/// makes `Ruleset::handle_access(AccessNet::from_all(ABI::V3))` return an error.\nimpl Access for AccessNet {\n    fn from_all(abi: ABI) -> BitFlags<Self> {\n        match abi {\n            ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 => BitFlags::EMPTY,\n            ABI::V4 | ABI::V5 | ABI::V6 => AccessNet::BindTcp | AccessNet::ConnectTcp,\n        }\n    }\n}\n\nimpl HandledAccess for AccessNet {}\n\nimpl PrivateHandledAccess for AccessNet {\n    fn ruleset_handle_access(\n        ruleset: &mut Ruleset,\n        access: BitFlags<Self>,\n    ) -> Result<(), HandleAccessesError> {\n        // We need to record the requested accesses for PrivateRule::check_consistency().\n        ruleset.requested_handled_net |= access;\n        ruleset.actual_handled_net |= match access\n            .try_compat(\n                ruleset.compat.abi(),\n                ruleset.compat.level,\n                &mut ruleset.compat.state,\n            )\n            .map_err(HandleAccessError::Compat)?\n        {\n            Some(a) => a,\n            None => return Ok(()),\n        };\n        Ok(())\n    }\n\n    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {\n        AddRulesError::Net(error)\n    }\n\n    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {\n        HandleAccessesError::Net(error)\n    }\n}\n\n/// Landlock rule for a network port.\n///\n/// # Example\n///\n/// ```\n/// use landlock::{AccessNet, NetPort};\n///\n/// fn bind_http() -> NetPort {\n///     NetPort::new(80, AccessNet::BindTcp)\n/// }\n/// ```\n#[derive(Debug)]\npub struct NetPort {\n    attr: uapi::landlock_net_port_attr,\n    // Only 16-bit port make sense for now.\n    port: u16,\n    allowed_access: BitFlags<AccessNet>,\n    compat_level: Option<CompatLevel>,\n}\n\n// If we need support for 32 or 64 ports, we'll add a new_32() or a new_64() method returning a\n// Result with a potential overflow error.\nimpl NetPort {\n    /// Creates a new TCP port rule.\n    ///\n    /// As defined by the Linux ABI, `port` with a value of `0` means that TCP bindings will be\n    /// allowed for a port range defined by `/proc/sys/net/ipv4/ip_local_port_range`.\n    pub fn new<A>(port: u16, access: A) -> Self\n    where\n        A: Into<BitFlags<AccessNet>>,\n    {\n        NetPort {\n            // Invalid access-rights until as_ptr() is called.\n            attr: unsafe { zeroed() },\n            port,\n            allowed_access: access.into(),\n            compat_level: None,\n        }\n    }\n}\n\nimpl Rule<AccessNet> for NetPort {}\n\nimpl PrivateRule<AccessNet> for NetPort {\n    const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_NET_PORT;\n\n    fn as_ptr(&mut self) -> *const libc::c_void {\n        self.attr.port = self.port as u64;\n        self.attr.allowed_access = self.allowed_access.bits();\n        &self.attr as *const _ as _\n    }\n\n    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {\n        // Checks that this rule doesn't contain a superset of the access-rights handled by the\n        // ruleset.  This check is about requested access-rights but not actual access-rights.\n        // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel\n        // (which is handled by Ruleset and RulesetCreated).\n        if ruleset.requested_handled_net.contains(self.allowed_access) {\n            Ok(())\n        } else {\n            Err(AddRuleError::UnhandledAccess {\n                access: self.allowed_access,\n                incompatible: self.allowed_access & !ruleset.requested_handled_net,\n            }\n            .into())\n        }\n    }\n}\n\n#[test]\nfn net_port_check_consistency() {\n    use crate::*;\n\n    let bind = AccessNet::BindTcp;\n    let bind_connect = bind | AccessNet::ConnectTcp;\n\n    assert!(matches!(\n        Ruleset::from(ABI::Unsupported)\n            .handle_access(bind)\n            .unwrap()\n            .create()\n            .unwrap()\n            .add_rule(NetPort::new(1, bind_connect))\n            .unwrap_err(),\n        RulesetError::AddRules(AddRulesError::Net(AddRuleError::UnhandledAccess { access, incompatible }))\n            if access == bind_connect && incompatible == AccessNet::ConnectTcp\n    ));\n}\n\nimpl TryCompat<AccessNet> for NetPort {\n    fn try_compat_children<L>(\n        mut self,\n        abi: ABI,\n        parent_level: L,\n        compat_state: &mut CompatState,\n    ) -> Result<Option<Self>, CompatError<AccessNet>>\n    where\n        L: Into<CompatLevel>,\n    {\n        // Checks with our own compatibility level, if any.\n        self.allowed_access = match self.allowed_access.try_compat(\n            abi,\n            self.tailored_compat_level(parent_level),\n            compat_state,\n        )? {\n            Some(a) => a,\n            None => return Ok(None),\n        };\n        Ok(Some(self))\n    }\n\n    fn try_compat_inner(\n        &mut self,\n        _abi: ABI,\n    ) -> Result<CompatResult<AccessNet>, CompatError<AccessNet>> {\n        Ok(CompatResult::Full)\n    }\n}\n\nimpl OptionCompatLevelMut for NetPort {\n    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {\n        &mut self.compat_level\n    }\n}\n\nimpl OptionCompatLevelMut for &mut NetPort {\n    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {\n        &mut self.compat_level\n    }\n}\n\nimpl Compatible for NetPort {}\n\nimpl Compatible for &mut NetPort {}\n"
  },
  {
    "path": "src/ruleset.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::compat::private::OptionCompatLevelMut;\nuse crate::{\n    uapi, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel, CompatState,\n    Compatibility, Compatible, CreateRulesetError, HandledAccess, LandlockStatus,\n    PrivateHandledAccess, RestrictSelfError, RulesetError, Scope, ScopeError, TryCompat,\n};\nuse std::io::Error;\nuse std::mem::size_of_val;\nuse std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};\n\n#[cfg(test)]\nuse crate::*;\n\n// Public interface without methods and which is impossible to implement outside this crate.\npub trait Rule<T>: PrivateRule<T>\nwhere\n    T: HandledAccess,\n{\n}\n\n// PrivateRule is not public outside this crate.\npub trait PrivateRule<T>\nwhere\n    Self: TryCompat<T> + Compatible,\n    T: HandledAccess,\n{\n    const TYPE_ID: uapi::landlock_rule_type;\n\n    /// Returns a raw pointer to the rule's inner attribute.\n    ///\n    /// The caller must ensure that the rule outlives the pointer this function returns, or else it\n    /// will end up pointing to garbage.\n    fn as_ptr(&mut self) -> *const libc::c_void;\n\n    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError>;\n}\n\n/// Enforcement status of a ruleset.\n#[derive(Debug, PartialEq, Eq)]\npub enum RulesetStatus {\n    /// All requested restrictions are enforced.\n    FullyEnforced,\n    /// Some requested restrictions are enforced,\n    /// following a best-effort approach.\n    PartiallyEnforced,\n    /// The running system doesn't support Landlock\n    /// or a subset of the requested Landlock features.\n    NotEnforced,\n}\n\nimpl From<CompatState> for RulesetStatus {\n    fn from(state: CompatState) -> Self {\n        match state {\n            CompatState::Init | CompatState::No | CompatState::Dummy => RulesetStatus::NotEnforced,\n            CompatState::Full => RulesetStatus::FullyEnforced,\n            CompatState::Partial => RulesetStatus::PartiallyEnforced,\n        }\n    }\n}\n\n// The Debug, PartialEq and Eq implementations are useful for crate users to debug and check the\n// result of a Landlock ruleset enforcement.\n/// Status of a [`RulesetCreated`]\n/// after calling [`restrict_self()`](RulesetCreated::restrict_self).\n#[derive(Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub struct RestrictionStatus {\n    /// Status of the Landlock ruleset enforcement.\n    pub ruleset: RulesetStatus,\n    /// Status of `prctl(2)`'s `PR_SET_NO_NEW_PRIVS` enforcement.\n    pub no_new_privs: bool,\n    /// Status of Landlock for the running kernel.\n    pub landlock: LandlockStatus,\n}\n\nfn prctl_set_no_new_privs() -> Result<(), Error> {\n    match unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } {\n        0 => Ok(()),\n        _ => Err(Error::last_os_error()),\n    }\n}\n\nfn support_no_new_privs() -> bool {\n    // Only Linux < 3.5 or kernel with seccomp filters should return an error.\n    matches!(\n        unsafe { libc::prctl(libc::PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) },\n        0 | 1\n    )\n}\n\n/// Landlock ruleset builder.\n///\n/// `Ruleset` enables to create a Landlock ruleset in a flexible way\n/// following the builder pattern.\n/// Most build steps return a [`Result`] with [`RulesetError`].\n///\n/// You should probably not create more than one ruleset per application.\n/// Creating multiple rulesets is only useful when gradually restricting an application\n/// (e.g., a first set of generic restrictions before reading any file,\n/// then a second set of tailored restrictions after reading the configuration).\n///\n/// # Simple example\n///\n/// Simple helper handling only Landlock-related errors.\n///\n/// ```\n/// use landlock::{\n///     Access, AccessFs, PathBeneath, PathFd, RestrictionStatus, Ruleset, RulesetAttr,\n///     RulesetCreatedAttr, RulesetError, ABI,\n/// };\n/// use std::os::unix::io::AsFd;\n///\n/// fn restrict_fd<T>(hierarchy: T) -> Result<RestrictionStatus, RulesetError>\n/// where\n///     T: AsFd,\n/// {\n///     // The Landlock ABI should be incremented (and tested) regularly.\n///     let abi = ABI::V1;\n///     let access_all = AccessFs::from_all(abi);\n///     let access_read = AccessFs::from_read(abi);\n///     Ok(Ruleset::default()\n///         .handle_access(access_all)?\n///         .create()?\n///         .add_rule(PathBeneath::new(hierarchy, access_read))?\n///         .restrict_self()?)\n/// }\n///\n/// let fd = PathFd::new(\"/home\").expect(\"failed to open /home\");\n/// let status = restrict_fd(fd).expect(\"failed to build the ruleset\");\n/// ```\n///\n/// # Generic example\n///\n/// More generic helper handling a set of file hierarchies\n/// and multiple types of error (i.e. [`RulesetError`](crate::RulesetError)\n/// and [`PathFdError`](crate::PathFdError).\n///\n/// ```\n/// use landlock::{\n///     Access, AccessFs, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,\n///     RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,\n/// };\n/// use thiserror::Error;\n///\n/// #[derive(Debug, Error)]\n/// enum MyRestrictError {\n///     #[error(transparent)]\n///     Ruleset(#[from] RulesetError),\n///     #[error(transparent)]\n///     AddRule(#[from] PathFdError),\n/// }\n///\n/// fn restrict_paths(hierarchies: &[&str]) -> Result<RestrictionStatus, MyRestrictError> {\n///     // The Landlock ABI should be incremented (and tested) regularly.\n///     let abi = ABI::V1;\n///     let access_all = AccessFs::from_all(abi);\n///     let access_read = AccessFs::from_read(abi);\n///     Ok(Ruleset::default()\n///         .handle_access(access_all)?\n///         .create()?\n///         .add_rules(\n///             hierarchies\n///                 .iter()\n///                 .map::<Result<_, MyRestrictError>, _>(|p| {\n///                     Ok(PathBeneath::new(PathFd::new(p)?, access_read))\n///                 }),\n///         )?\n///         .restrict_self()?)\n/// }\n///\n/// let status = restrict_paths(&[\"/usr\", \"/home\"]).expect(\"failed to build the ruleset\");\n/// ```\n#[derive(Debug)]\npub struct Ruleset {\n    pub(crate) requested_handled_fs: BitFlags<AccessFs>,\n    pub(crate) requested_handled_net: BitFlags<AccessNet>,\n    pub(crate) requested_scoped: BitFlags<Scope>,\n    pub(crate) actual_handled_fs: BitFlags<AccessFs>,\n    pub(crate) actual_handled_net: BitFlags<AccessNet>,\n    pub(crate) actual_scoped: BitFlags<Scope>,\n    pub(crate) compat: Compatibility,\n}\n\nimpl From<Compatibility> for Ruleset {\n    fn from(compat: Compatibility) -> Self {\n        Ruleset {\n            // Non-working default handled FS accesses to force users to set them explicitely.\n            requested_handled_fs: Default::default(),\n            requested_handled_net: Default::default(),\n            requested_scoped: Default::default(),\n            actual_handled_fs: Default::default(),\n            actual_handled_net: Default::default(),\n            actual_scoped: Default::default(),\n            compat,\n        }\n    }\n}\n\n#[cfg(test)]\nimpl From<ABI> for Ruleset {\n    fn from(abi: ABI) -> Self {\n        Ruleset::from(Compatibility::from(abi))\n    }\n}\n\n#[test]\nfn ruleset_add_rule_iter() {\n    assert!(matches!(\n        Ruleset::from(ABI::Unsupported)\n            .handle_access(AccessFs::Execute)\n            .unwrap()\n            .create()\n            .unwrap()\n            .add_rule(PathBeneath::new(\n                PathFd::new(\"/\").unwrap(),\n                AccessFs::ReadFile\n            ))\n            .unwrap_err(),\n        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))\n    ));\n}\n\nimpl Default for Ruleset {\n    /// Returns a new `Ruleset`.\n    /// This call automatically probes the running kernel to know if it supports Landlock.\n    ///\n    /// To be able to successfully call [`create()`](Ruleset::create),\n    /// it is required to set the handled accesses with\n    /// [`handle_access()`](Ruleset::handle_access).\n    fn default() -> Self {\n        // The API should be future-proof: one Rust program or library should have the same\n        // behavior if built with an old or a newer crate (e.g. with an extended ruleset_attr\n        // enum).  It should then not be possible to give an \"all-possible-handled-accesses\" to the\n        // Ruleset builder because this value would be relative to the running kernel.\n        Compatibility::new().into()\n    }\n}\n\nimpl Ruleset {\n    #[allow(clippy::new_without_default)]\n    #[deprecated(note = \"Use Ruleset::default() instead\")]\n    pub fn new() -> Self {\n        Ruleset::default()\n    }\n\n    /// Attempts to create a real Landlock ruleset (if supported by the running kernel).\n    /// The returned [`RulesetCreated`] is also a builder.\n    ///\n    /// On error, returns a wrapped [`CreateRulesetError`].\n    pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {\n        let body = || -> Result<RulesetCreated, CreateRulesetError> {\n            match self.compat.state {\n                CompatState::Init => {\n                    // Checks that there is at least one requested access (e.g.\n                    // requested_handled_fs): one call to handle_access().\n                    Err(CreateRulesetError::MissingHandledAccess)\n                }\n                CompatState::No | CompatState::Dummy => {\n                    // There is at least one requested access.\n                    #[cfg(test)]\n                    assert!(\n                        !self.requested_handled_fs.is_empty()\n                            || !self.requested_handled_net.is_empty()\n                            || !self.requested_scoped.is_empty()\n                    );\n\n                    // CompatState::No should be handled as CompatState::Dummy because it is not\n                    // possible to create an actual ruleset.\n                    self.compat.update(CompatState::Dummy);\n                    match self.compat.level.into() {\n                        CompatLevel::HardRequirement => {\n                            Err(CreateRulesetError::MissingHandledAccess)\n                        }\n                        _ => Ok(RulesetCreated::new(self, None)),\n                    }\n                }\n                CompatState::Full | CompatState::Partial => {\n                    // There is at least one actual handled access.\n                    #[cfg(test)]\n                    assert!(\n                        !self.actual_handled_fs.is_empty()\n                            || !self.actual_handled_net.is_empty()\n                            || !self.actual_scoped.is_empty()\n                    );\n\n                    let attr = uapi::landlock_ruleset_attr {\n                        handled_access_fs: self.actual_handled_fs.bits(),\n                        handled_access_net: self.actual_handled_net.bits(),\n                        scoped: self.actual_scoped.bits(),\n                    };\n                    match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {\n                        fd if fd >= 0 => Ok(RulesetCreated::new(\n                            self,\n                            Some(unsafe { OwnedFd::from_raw_fd(fd) }),\n                        )),\n                        _ => Err(CreateRulesetError::CreateRulesetCall {\n                            source: Error::last_os_error(),\n                        }),\n                    }\n                }\n            }\n        };\n        Ok(body()?)\n    }\n}\n\nimpl OptionCompatLevelMut for Ruleset {\n    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {\n        &mut self.compat.level\n    }\n}\n\nimpl OptionCompatLevelMut for &mut Ruleset {\n    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {\n        &mut self.compat.level\n    }\n}\n\nimpl Compatible for Ruleset {}\n\nimpl Compatible for &mut Ruleset {}\n\nimpl AsMut<Ruleset> for Ruleset {\n    fn as_mut(&mut self) -> &mut Ruleset {\n        self\n    }\n}\n\n// Tests unambiguous type.\n#[test]\nfn ruleset_as_mut() {\n    let mut ruleset = Ruleset::from(ABI::Unsupported);\n    let _ = ruleset.as_mut();\n\n    let mut ruleset_created = Ruleset::from(ABI::Unsupported)\n        .handle_access(AccessFs::Execute)\n        .unwrap()\n        .create()\n        .unwrap();\n    let _ = ruleset_created.as_mut();\n}\n\npub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {\n    /// Attempts to add a set of access rights that will be supported by this ruleset.\n    /// By default, all actions requiring these access rights will be denied.\n    /// Consecutive calls to `handle_access()` will be interpreted as logical ORs\n    /// with the previous handled accesses.\n    ///\n    /// On error, returns a wrapped [`HandleAccessesError`](crate::HandleAccessesError).\n    /// E.g., `RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError<AccessFs>))`\n    fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>\n    where\n        T: Into<BitFlags<U>>,\n        U: HandledAccess + PrivateHandledAccess,\n    {\n        U::ruleset_handle_access(self.as_mut(), access.into())?;\n        Ok(self)\n    }\n\n    /// Attempts to add a set of scopes that will be supported by this ruleset.\n    /// Consecutive calls to `scope()` will be interpreted as logical ORs\n    /// with the previous scopes.\n    ///\n    /// On error, returns a wrapped [`ScopeError`](crate::ScopeError).\n    /// E.g., `RulesetError::Scope(ScopeError)`\n    fn scope<T>(mut self, scope: T) -> Result<Self, RulesetError>\n    where\n        T: Into<BitFlags<Scope>>,\n    {\n        let scope = scope.into();\n        let ruleset = self.as_mut();\n        ruleset.requested_scoped |= scope;\n        if let Some(a) = scope\n            .try_compat(\n                ruleset.compat.abi(),\n                ruleset.compat.level,\n                &mut ruleset.compat.state,\n            )\n            .map_err(ScopeError::Compat)?\n        {\n            ruleset.actual_scoped |= a;\n        }\n        Ok(self)\n    }\n}\n\nimpl RulesetAttr for Ruleset {}\n\nimpl RulesetAttr for &mut Ruleset {}\n\n#[test]\nfn ruleset_attr() {\n    let mut ruleset = Ruleset::from(ABI::Unsupported);\n    let ruleset_ref = &mut ruleset;\n\n    // Can pass this reference to prepare the ruleset...\n    ruleset_ref\n        .set_compatibility(CompatLevel::BestEffort)\n        .handle_access(AccessFs::Execute)\n        .unwrap()\n        .handle_access(AccessFs::ReadFile)\n        .unwrap();\n\n    // ...and finally create the ruleset (thanks to non-lexical lifetimes).\n    ruleset\n        .set_compatibility(CompatLevel::BestEffort)\n        .handle_access(AccessFs::Execute)\n        .unwrap()\n        .handle_access(AccessFs::WriteFile)\n        .unwrap()\n        .create()\n        .unwrap();\n}\n\n#[test]\nfn ruleset_created_handle_access_fs() {\n    let access = make_bitflags!(AccessFs::{Execute | ReadDir});\n\n    // Tests AccessFs::ruleset_handle_access()\n    let ruleset = Ruleset::from(ABI::V1).handle_access(access).unwrap();\n    assert_eq!(ruleset.requested_handled_fs, access);\n    assert_eq!(ruleset.actual_handled_fs, access);\n\n    // Tests composition (binary OR) of handled accesses.\n    let ruleset = Ruleset::from(ABI::V1)\n        .handle_access(AccessFs::Execute)\n        .unwrap()\n        .handle_access(AccessFs::ReadDir)\n        .unwrap()\n        .handle_access(AccessFs::Execute)\n        .unwrap();\n    assert_eq!(ruleset.requested_handled_fs, access);\n    assert_eq!(ruleset.actual_handled_fs, access);\n\n    // Tests that only the required handled accesses are reported as incompatible:\n    // access should not contains AccessFs::Execute.\n    assert!(matches!(Ruleset::from(ABI::Unsupported)\n        .handle_access(AccessFs::Execute)\n        .unwrap()\n        .set_compatibility(CompatLevel::HardRequirement)\n        .handle_access(AccessFs::ReadDir)\n        .unwrap_err(),\n        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(\n            CompatError::Access(AccessError::Incompatible { access })\n        ))) if access == AccessFs::ReadDir\n    ));\n}\n\n#[test]\nfn ruleset_created_handle_access_net_tcp() {\n    let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});\n\n    // Tests AccessNet::ruleset_handle_access() with ABI that doesn't support TCP rights.\n    let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();\n    assert_eq!(ruleset.requested_handled_net, access);\n    assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);\n\n    // Tests AccessNet::ruleset_handle_access() with ABI that supports TCP rights.\n    let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();\n    assert_eq!(ruleset.requested_handled_net, access);\n    assert_eq!(ruleset.actual_handled_net, access);\n\n    // Tests composition (binary OR) of handled accesses.\n    let ruleset = Ruleset::from(ABI::V4)\n        .handle_access(AccessNet::BindTcp)\n        .unwrap()\n        .handle_access(AccessNet::ConnectTcp)\n        .unwrap()\n        .handle_access(AccessNet::BindTcp)\n        .unwrap();\n    assert_eq!(ruleset.requested_handled_net, access);\n    assert_eq!(ruleset.actual_handled_net, access);\n\n    // Tests that only the required handled accesses are reported as incompatible:\n    // access should not contains AccessNet::BindTcp.\n    assert!(matches!(Ruleset::from(ABI::Unsupported)\n        .handle_access(AccessNet::BindTcp)\n        .unwrap()\n        .set_compatibility(CompatLevel::HardRequirement)\n        .handle_access(AccessNet::ConnectTcp)\n        .unwrap_err(),\n        RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(\n            CompatError::Access(AccessError::Incompatible { access })\n        ))) if access == AccessNet::ConnectTcp\n    ));\n}\n\n#[test]\nfn ruleset_created_scope() {\n    let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});\n\n    // Tests Ruleset::scope() with ABI that doesn't support scopes.\n    let ruleset = Ruleset::from(ABI::V5).scope(scopes).unwrap();\n    assert_eq!(ruleset.requested_scoped, scopes);\n    assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);\n\n    // Tests Ruleset::scope() with ABI that supports scopes.\n    let ruleset = Ruleset::from(ABI::V6).scope(scopes).unwrap();\n    assert_eq!(ruleset.requested_scoped, scopes);\n    assert_eq!(ruleset.actual_scoped, scopes);\n\n    // Tests composition (binary OR) of scopes.\n    let ruleset = Ruleset::from(ABI::V6)\n        .scope(Scope::AbstractUnixSocket)\n        .unwrap()\n        .scope(Scope::Signal)\n        .unwrap()\n        .scope(Scope::AbstractUnixSocket)\n        .unwrap();\n    assert_eq!(ruleset.requested_scoped, scopes);\n    assert_eq!(ruleset.actual_scoped, scopes);\n\n    // Tests that only the required scopes are reported as incompatible:\n    // scope should not contain Scope::AbstractUnixSocket.\n    assert!(matches!(Ruleset::from(ABI::Unsupported)\n        .scope(Scope::AbstractUnixSocket)\n        .unwrap()\n        .set_compatibility(CompatLevel::HardRequirement)\n        .scope(Scope::Signal)\n        .unwrap_err(),\n        RulesetError::Scope(ScopeError::Compat(\n            CompatError::Access(AccessError::Incompatible { access })\n        )) if access == Scope::Signal\n    ));\n}\n\n#[test]\nfn ruleset_created_fs_net_scope() {\n    let access_fs = make_bitflags!(AccessFs::{Execute | ReadDir});\n    let access_net = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});\n    let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});\n\n    // Tests composition (binary OR) of handled accesses.\n    let ruleset = Ruleset::from(ABI::V5)\n        .handle_access(access_fs)\n        .unwrap()\n        .scope(scopes)\n        .unwrap()\n        .handle_access(access_net)\n        .unwrap();\n    assert_eq!(ruleset.requested_handled_fs, access_fs);\n    assert_eq!(ruleset.actual_handled_fs, access_fs);\n    assert_eq!(ruleset.requested_handled_net, access_net);\n    assert_eq!(ruleset.actual_handled_net, access_net);\n    assert_eq!(ruleset.requested_scoped, scopes);\n    assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);\n\n    // Tests composition (binary OR) of handled accesses and scopes.\n    let ruleset = Ruleset::from(ABI::V6)\n        .handle_access(access_fs)\n        .unwrap()\n        .scope(scopes)\n        .unwrap()\n        .handle_access(access_net)\n        .unwrap();\n    assert_eq!(ruleset.requested_handled_fs, access_fs);\n    assert_eq!(ruleset.actual_handled_fs, access_fs);\n    assert_eq!(ruleset.requested_handled_net, access_net);\n    assert_eq!(ruleset.actual_handled_net, access_net);\n    assert_eq!(ruleset.requested_scoped, scopes);\n    assert_eq!(ruleset.actual_scoped, scopes);\n}\n\nimpl OptionCompatLevelMut for RulesetCreated {\n    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {\n        &mut self.compat.level\n    }\n}\n\nimpl OptionCompatLevelMut for &mut RulesetCreated {\n    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {\n        &mut self.compat.level\n    }\n}\n\nimpl Compatible for RulesetCreated {}\n\nimpl Compatible for &mut RulesetCreated {}\n\npub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {\n    /// Attempts to add a new rule to the ruleset.\n    ///\n    /// On error, returns a wrapped [`AddRulesError`].\n    fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>\n    where\n        T: Rule<U>,\n        U: HandledAccess + PrivateHandledAccess,\n    {\n        let body = || -> Result<Self, AddRulesError> {\n            let self_ref = self.as_mut();\n            rule.check_consistency(self_ref)?;\n            let mut compat_rule = match rule\n                .try_compat(\n                    self_ref.compat.abi(),\n                    self_ref.compat.level,\n                    &mut self_ref.compat.state,\n                )\n                .map_err(AddRuleError::Compat)?\n            {\n                Some(r) => r,\n                None => return Ok(self),\n            };\n            match self_ref.compat.state {\n                CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),\n                CompatState::Full | CompatState::Partial => {\n                    #[cfg(test)]\n                    assert!(self_ref.fd.is_some());\n                    let fd = self_ref.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);\n                    match unsafe {\n                        uapi::landlock_add_rule(fd, T::TYPE_ID, compat_rule.as_ptr(), 0)\n                    } {\n                        0 => Ok(self),\n                        _ => Err(AddRuleError::<U>::AddRuleCall {\n                            source: Error::last_os_error(),\n                        }\n                        .into()),\n                    }\n                }\n            }\n        };\n        Ok(body()?)\n    }\n\n    /// Attempts to add a set of new rules to the ruleset.\n    ///\n    /// On error, returns a (double) wrapped [`AddRulesError`].\n    ///\n    /// # Example\n    ///\n    /// Create a custom iterator to read paths from environment variable.\n    ///\n    /// ```\n    /// use landlock::{\n    ///     Access, AccessFs, BitFlags, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,\n    ///     RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,\n    /// };\n    /// use std::env;\n    /// use std::ffi::OsStr;\n    /// use std::os::unix::ffi::{OsStrExt, OsStringExt};\n    /// use thiserror::Error;\n    ///\n    /// #[derive(Debug, Error)]\n    /// enum PathEnvError<'a> {\n    ///     #[error(transparent)]\n    ///     Ruleset(#[from] RulesetError),\n    ///     #[error(transparent)]\n    ///     AddRuleIter(#[from] PathFdError),\n    ///     #[error(\"missing environment variable {0}\")]\n    ///     MissingVar(&'a str),\n    /// }\n    ///\n    /// struct PathEnv {\n    ///     paths: Vec<u8>,\n    ///     access: BitFlags<AccessFs>,\n    /// }\n    ///\n    /// impl PathEnv {\n    ///     // env_var is the name of an environment variable\n    ///     // containing paths requested to be allowed.\n    ///     // Paths are separated with \":\", e.g. \"/bin:/lib:/usr:/proc\".\n    ///     // In case an empty string is provided,\n    ///     // no restrictions are applied.\n    ///     // `access` is the set of access rights allowed for each of the parsed paths.\n    ///     fn new<'a>(\n    ///         env_var: &'a str, access: BitFlags<AccessFs>\n    ///     ) -> Result<Self, PathEnvError<'a>> {\n    ///         Ok(Self {\n    ///             paths: env::var_os(env_var)\n    ///                 .ok_or(PathEnvError::MissingVar(env_var))?\n    ///                 .into_vec(),\n    ///             access,\n    ///         })\n    ///     }\n    ///\n    ///     fn iter(\n    ///         &self,\n    ///     ) -> impl Iterator<Item = Result<PathBeneath<PathFd>, PathEnvError<'static>>> + '_ {\n    ///         let is_empty = self.paths.is_empty();\n    ///         self.paths\n    ///             .split(|b| *b == b':')\n    ///             // Skips the first empty element from of an empty string.\n    ///             .skip_while(move |_| is_empty)\n    ///             .map(OsStr::from_bytes)\n    ///             .map(move |path|\n    ///                 Ok(PathBeneath::new(PathFd::new(path)?, self.access)))\n    ///     }\n    /// }\n    ///\n    /// fn restrict_env() -> Result<RestrictionStatus, PathEnvError<'static>> {\n    ///     Ok(Ruleset::default()\n    ///         .handle_access(AccessFs::from_all(ABI::V1))?\n    ///         .create()?\n    ///         // In the shell: export EXECUTABLE_PATH=\"/usr:/bin:/sbin\"\n    ///         .add_rules(PathEnv::new(\"EXECUTABLE_PATH\", AccessFs::Execute.into())?.iter())?\n    ///         .restrict_self()?)\n    /// }\n    /// ```\n    fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>\n    where\n        I: IntoIterator<Item = Result<T, E>>,\n        T: Rule<U>,\n        U: HandledAccess + PrivateHandledAccess,\n        E: From<RulesetError>,\n    {\n        for rule in rules {\n            self = self.add_rule(rule?)?;\n        }\n        Ok(self)\n    }\n\n    /// Configures the ruleset to call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS` command\n    /// in [`restrict_self()`](RulesetCreated::restrict_self).\n    ///\n    /// This `prctl(2)` call is never ignored, even if an error was encountered on a [`Ruleset`] or\n    /// [`RulesetCreated`] method call while [`CompatLevel::SoftRequirement`] was set.\n    fn set_no_new_privs(mut self, no_new_privs: bool) -> Self {\n        <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = no_new_privs;\n        self\n    }\n}\n\n/// Ruleset created with [`Ruleset::create()`].\n#[derive(Debug)]\npub struct RulesetCreated {\n    fd: Option<OwnedFd>,\n    no_new_privs: bool,\n    pub(crate) requested_handled_fs: BitFlags<AccessFs>,\n    pub(crate) requested_handled_net: BitFlags<AccessNet>,\n    compat: Compatibility,\n}\n\nimpl RulesetCreated {\n    pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {\n        // The compatibility state is initialized by Ruleset::create().\n        #[cfg(test)]\n        assert!(!matches!(ruleset.compat.state, CompatState::Init));\n\n        RulesetCreated {\n            fd,\n            no_new_privs: true,\n            requested_handled_fs: ruleset.requested_handled_fs,\n            requested_handled_net: ruleset.requested_handled_net,\n            compat: ruleset.compat,\n        }\n    }\n\n    /// Attempts to restrict the calling thread with the ruleset\n    /// according to the best-effort configuration\n    /// (see [`RulesetCreated::set_compatibility()`] and [`CompatLevel::BestEffort`]).\n    /// Call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS`\n    /// according to the ruleset configuration.\n    ///\n    /// On error, returns a wrapped [`RestrictSelfError`].\n    pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {\n        let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {\n            // Enforce no_new_privs even if something failed with SoftRequirement. The rationale is\n            // that no_new_privs should not be an issue on its own if it is not explicitly\n            // deactivated.\n            let enforced_nnp = if self.no_new_privs {\n                if let Err(e) = prctl_set_no_new_privs() {\n                    match self.compat.level.into() {\n                        CompatLevel::BestEffort => {}\n                        CompatLevel::SoftRequirement => {\n                            self.compat.update(CompatState::Dummy);\n                        }\n                        CompatLevel::HardRequirement => {\n                            return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });\n                        }\n                    }\n                    // To get a consistent behavior, calls this prctl whether or not\n                    // Landlock is supported by the running kernel.\n                    let support_nnp = support_no_new_privs();\n                    match self.compat.state {\n                        // It should not be an error for kernel (older than 3.5) not supporting\n                        // no_new_privs.\n                        CompatState::Init | CompatState::No | CompatState::Dummy => {\n                            if support_nnp {\n                                // The kernel seems to be between 3.5 (included) and 5.13 (excluded),\n                                // or Landlock is not enabled; no_new_privs should be supported anyway.\n                                return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });\n                            }\n                        }\n                        // A kernel supporting Landlock should also support no_new_privs (unless\n                        // filtered by seccomp).\n                        CompatState::Full | CompatState::Partial => {\n                            return Err(RestrictSelfError::SetNoNewPrivsCall { source: e })\n                        }\n                    }\n                    false\n                } else {\n                    true\n                }\n            } else {\n                false\n            };\n\n            match self.compat.state {\n                CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {\n                    ruleset: self.compat.state.into(),\n                    landlock: self.compat.status(),\n                    no_new_privs: enforced_nnp,\n                }),\n                CompatState::Full | CompatState::Partial => {\n                    #[cfg(test)]\n                    assert!(self.fd.is_some());\n                    // Does not consume ruleset FD, which will be automatically closed after this block.\n                    let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);\n                    match unsafe { uapi::landlock_restrict_self(fd, 0) } {\n                        0 => {\n                            self.compat.update(CompatState::Full);\n                            Ok(RestrictionStatus {\n                                ruleset: self.compat.state.into(),\n                                landlock: self.compat.status(),\n                                no_new_privs: enforced_nnp,\n                            })\n                        }\n                        // TODO: match specific Landlock restrict self errors\n                        _ => Err(RestrictSelfError::RestrictSelfCall {\n                            source: Error::last_os_error(),\n                        }),\n                    }\n                }\n            }\n        };\n        Ok(body()?)\n    }\n\n    /// Creates a new `RulesetCreated` instance by duplicating the underlying file descriptor.\n    /// Rule modification will affect both `RulesetCreated` instances simultaneously.\n    ///\n    /// On error, returns [`std::io::Error`].\n    pub fn try_clone(&self) -> std::io::Result<Self> {\n        Ok(RulesetCreated {\n            fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,\n            no_new_privs: self.no_new_privs,\n            requested_handled_fs: self.requested_handled_fs,\n            requested_handled_net: self.requested_handled_net,\n            compat: self.compat,\n        })\n    }\n}\n\nimpl From<RulesetCreated> for Option<OwnedFd> {\n    fn from(ruleset: RulesetCreated) -> Self {\n        ruleset.fd\n    }\n}\n\n#[test]\nfn ruleset_created_ownedfd_none() {\n    let ruleset = Ruleset::from(ABI::Unsupported)\n        .handle_access(AccessFs::Execute)\n        .unwrap()\n        .create()\n        .unwrap();\n    let fd: Option<OwnedFd> = ruleset.into();\n    assert!(fd.is_none());\n}\n\nimpl AsMut<RulesetCreated> for RulesetCreated {\n    fn as_mut(&mut self) -> &mut RulesetCreated {\n        self\n    }\n}\n\nimpl RulesetCreatedAttr for RulesetCreated {}\n\nimpl RulesetCreatedAttr for &mut RulesetCreated {}\n\n#[test]\nfn ruleset_created_attr() {\n    let mut ruleset_created = Ruleset::from(ABI::Unsupported)\n        .handle_access(AccessFs::Execute)\n        .unwrap()\n        .create()\n        .unwrap();\n    let ruleset_created_ref = &mut ruleset_created;\n\n    // Can pass this reference to populate the ruleset...\n    ruleset_created_ref\n        .set_compatibility(CompatLevel::BestEffort)\n        .add_rule(PathBeneath::new(\n            PathFd::new(\"/usr\").unwrap(),\n            AccessFs::Execute,\n        ))\n        .unwrap()\n        .add_rule(PathBeneath::new(\n            PathFd::new(\"/etc\").unwrap(),\n            AccessFs::Execute,\n        ))\n        .unwrap();\n\n    // ...and finally restrict with the last rules (thanks to non-lexical lifetimes).\n    assert_eq!(\n        ruleset_created\n            .set_compatibility(CompatLevel::BestEffort)\n            .add_rule(PathBeneath::new(\n                PathFd::new(\"/tmp\").unwrap(),\n                AccessFs::Execute,\n            ))\n            .unwrap()\n            .add_rule(PathBeneath::new(\n                PathFd::new(\"/var\").unwrap(),\n                AccessFs::Execute,\n            ))\n            .unwrap()\n            .restrict_self()\n            .unwrap(),\n        RestrictionStatus {\n            ruleset: RulesetStatus::NotEnforced,\n            landlock: LandlockStatus::NotImplemented,\n            no_new_privs: true,\n        }\n    );\n}\n\n#[test]\nfn ruleset_compat_dummy() {\n    for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {\n        println!(\"level: {:?}\", level);\n\n        // ABI:Unsupported does not support AccessFs::Execute.\n        let ruleset = Ruleset::from(ABI::Unsupported);\n        assert_eq!(ruleset.compat.state, CompatState::Init);\n\n        let ruleset = ruleset.set_compatibility(level);\n        assert_eq!(ruleset.compat.state, CompatState::Init);\n\n        let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();\n        assert_eq!(\n            ruleset.compat.state,\n            match level {\n                CompatLevel::BestEffort => CompatState::No,\n                CompatLevel::SoftRequirement => CompatState::Dummy,\n                _ => unreachable!(),\n            }\n        );\n\n        let ruleset_created = ruleset.create().unwrap();\n        // Because the compatibility state was either No or Dummy, calling create() updates it to\n        // Dummy.\n        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);\n\n        let ruleset_created = ruleset_created\n            .add_rule(PathBeneath::new(\n                PathFd::new(\"/usr\").unwrap(),\n                AccessFs::Execute,\n            ))\n            .unwrap();\n        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);\n    }\n}\n\n#[test]\nfn ruleset_compat_partial() {\n    // CompatLevel::BestEffort\n    let ruleset = Ruleset::from(ABI::V1);\n    assert_eq!(ruleset.compat.state, CompatState::Init);\n\n    // ABI::V1 does not support AccessFs::Refer.\n    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();\n    assert_eq!(ruleset.compat.state, CompatState::No);\n\n    let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();\n    assert_eq!(ruleset.compat.state, CompatState::Partial);\n\n    // Requesting to handle another unsupported handled access does not change anything.\n    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();\n    assert_eq!(ruleset.compat.state, CompatState::Partial);\n}\n\n#[test]\nfn ruleset_unsupported() {\n    assert_eq!(\n        Ruleset::from(ABI::Unsupported)\n            // BestEffort for Ruleset.\n            .handle_access(AccessFs::Execute)\n            .unwrap()\n            .create()\n            .unwrap()\n            .restrict_self()\n            .unwrap(),\n        RestrictionStatus {\n            ruleset: RulesetStatus::NotEnforced,\n            landlock: LandlockStatus::NotImplemented,\n            // With BestEffort, no_new_privs is still enabled.\n            no_new_privs: true,\n        }\n    );\n\n    assert_eq!(\n        Ruleset::from(ABI::Unsupported)\n            // SoftRequirement for Ruleset.\n            .set_compatibility(CompatLevel::SoftRequirement)\n            .handle_access(AccessFs::Execute)\n            .unwrap()\n            .create()\n            .unwrap()\n            .restrict_self()\n            .unwrap(),\n        RestrictionStatus {\n            ruleset: RulesetStatus::NotEnforced,\n            landlock: LandlockStatus::NotImplemented,\n            // With SoftRequirement, no_new_privs is still enabled.\n            no_new_privs: true,\n        }\n    );\n\n    // Missing handled access because of the compatibility level.\n    matches!(\n        Ruleset::from(ABI::Unsupported)\n            // HardRequirement for Ruleset.\n            .set_compatibility(CompatLevel::HardRequirement)\n            .handle_access(AccessFs::Execute)\n            .unwrap_err(),\n        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)\n    );\n\n    // Missing scope access because of the compatibility level.\n    matches!(\n        Ruleset::from(ABI::Unsupported)\n            // HardRequirement for Ruleset.\n            .set_compatibility(CompatLevel::HardRequirement)\n            .scope(Scope::Signal)\n            .unwrap_err(),\n        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)\n    );\n\n    assert_eq!(\n        Ruleset::from(ABI::Unsupported)\n            .handle_access(AccessFs::Execute)\n            .unwrap()\n            .create()\n            .unwrap()\n            // SoftRequirement for RulesetCreated without any rule.\n            .set_compatibility(CompatLevel::SoftRequirement)\n            .restrict_self()\n            .unwrap(),\n        RestrictionStatus {\n            ruleset: RulesetStatus::NotEnforced,\n            landlock: LandlockStatus::NotImplemented,\n            // With SoftRequirement, no_new_privs is untouched if there is no error (e.g. no rule).\n            no_new_privs: true,\n        }\n    );\n\n    // Don't explicitly call create() on a CI that doesn't support Landlock.\n    if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {\n        assert_eq!(\n            Ruleset::from(ABI::V1)\n                .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))\n                .unwrap()\n                .create()\n                .unwrap()\n                // SoftRequirement for RulesetCreated with a rule.\n                .set_compatibility(CompatLevel::SoftRequirement)\n                .add_rule(PathBeneath::new(PathFd::new(\"/\").unwrap(), AccessFs::Refer))\n                .unwrap()\n                .restrict_self()\n                .unwrap(),\n            RestrictionStatus {\n                ruleset: RulesetStatus::NotEnforced,\n                landlock: LandlockStatus::Available {\n                    effective_abi: ABI::V1,\n                    kernel_abi: None,\n                },\n                // With SoftRequirement, no_new_privs is still enabled, even if there is an error\n                // (e.g. unsupported access right).\n                no_new_privs: true,\n            }\n        );\n    }\n\n    assert_eq!(\n        Ruleset::from(ABI::Unsupported)\n            .handle_access(AccessFs::Execute)\n            .unwrap()\n            .create()\n            .unwrap()\n            .set_no_new_privs(false)\n            .restrict_self()\n            .unwrap(),\n        RestrictionStatus {\n            ruleset: RulesetStatus::NotEnforced,\n            landlock: LandlockStatus::NotImplemented,\n            no_new_privs: false,\n        }\n    );\n\n    // Checks empty handled access with moot ruleset.\n    assert!(matches!(\n        Ruleset::from(ABI::Unsupported)\n            // Empty access-rights\n            .handle_access(AccessFs::from_all(ABI::Unsupported))\n            .unwrap_err(),\n        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(\n            CompatError::Access(AccessError::Empty)\n        )))\n    ));\n\n    assert!(matches!(\n        Ruleset::from(ABI::Unsupported)\n            // No handle_access() nor scope() call.\n            .create()\n            .unwrap_err(),\n        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)\n    ));\n\n    // Checks empty handled access with minimal ruleset.\n    assert!(matches!(\n        Ruleset::from(ABI::V1)\n            // Empty access-rights\n            .handle_access(AccessFs::from_all(ABI::Unsupported))\n            .unwrap_err(),\n        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(\n            CompatError::Access(AccessError::Empty)\n        )))\n    ));\n\n    // Checks empty scope with moot ruleset.\n    assert!(matches!(\n        Ruleset::from(ABI::Unsupported)\n            .scope(Scope::from_all(ABI::Unsupported))\n            .unwrap_err(),\n        RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))\n    ));\n\n    // Checks empty scope with minimal ruleset.\n    assert!(matches!(\n        Ruleset::from(ABI::V1)\n            .scope(Scope::from_all(ABI::Unsupported))\n            .unwrap_err(),\n        RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))\n    ));\n\n    // Scope with SoftRequirement on unsupported ABI: silently dropped, state becomes Dummy.\n    let ruleset = Ruleset::from(ABI::V1)\n        .handle_access(AccessFs::Execute)\n        .unwrap()\n        .set_compatibility(CompatLevel::SoftRequirement)\n        .scope(Scope::Signal)\n        .unwrap();\n    assert_eq!(ruleset.requested_scoped, BitFlags::from(Scope::Signal));\n    assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);\n\n    // Tests inconsistency between the ruleset handled access-rights and the rule access-rights.\n    for handled_access in &[\n        make_bitflags!(AccessFs::{Execute | WriteFile}),\n        AccessFs::Execute.into(),\n    ] {\n        let ruleset = Ruleset::from(ABI::V1)\n            .handle_access(*handled_access)\n            .unwrap();\n        // Fakes a call to create() to test without involving the kernel (i.e. no\n        // landlock_ruleset_create() call).\n        let ruleset_created = RulesetCreated::new(ruleset, None);\n        assert!(matches!(\n            ruleset_created\n                .add_rule(PathBeneath::new(\n                    PathFd::new(\"/\").unwrap(),\n                    AccessFs::ReadFile\n                ))\n                .unwrap_err(),\n            RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))\n        ));\n    }\n}\n\n#[test]\nfn ignore_abi_v2_with_abi_v1() {\n    // We don't need kernel/CI support for Landlock because no related syscalls should actually be\n    // performed.\n    assert_eq!(\n        Ruleset::from(ABI::V1)\n            .set_compatibility(CompatLevel::HardRequirement)\n            .handle_access(AccessFs::from_all(ABI::V1))\n            .unwrap()\n            .set_compatibility(CompatLevel::SoftRequirement)\n            // Because Ruleset only supports V1, Refer will be ignored.\n            .handle_access(AccessFs::Refer)\n            .unwrap()\n            .create()\n            .unwrap()\n            .add_rule(PathBeneath::new(\n                PathFd::new(\"/tmp\").unwrap(),\n                AccessFs::from_all(ABI::V2)\n            ))\n            .unwrap()\n            .add_rule(PathBeneath::new(\n                PathFd::new(\"/usr\").unwrap(),\n                make_bitflags!(AccessFs::{ReadFile | ReadDir})\n            ))\n            .unwrap()\n            .restrict_self()\n            .unwrap(),\n        RestrictionStatus {\n            ruleset: RulesetStatus::NotEnforced,\n            landlock: LandlockStatus::Available {\n                effective_abi: ABI::V1,\n                kernel_abi: None,\n            },\n            no_new_privs: true,\n        }\n    );\n}\n\n#[test]\nfn unsupported_handled_access() {\n    matches!(\n        Ruleset::from(ABI::V3)\n            .handle_access(AccessNet::from_all(ABI::V3))\n            .unwrap_err(),\n        RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(\n            CompatError::Access(AccessError::Empty)\n        )))\n    );\n}\n\n#[test]\nfn unsupported_handled_access_errno() {\n    assert_eq!(\n        Errno::from(\n            Ruleset::from(ABI::V3)\n                .handle_access(AccessNet::from_all(ABI::V3))\n                .unwrap_err()\n        ),\n        Errno::new(libc::EINVAL)\n    );\n}\n"
  },
  {
    "path": "src/scope.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::{uapi, Access, ABI};\nuse enumflags2::{bitflags, BitFlags};\n\n/// Scope right.\n///\n/// Each variant of `Scope` is a\n/// [scope flag](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#scope-flags).\n/// A set of scopes can be created with [`BitFlags<Scope>`](BitFlags).\n///\n/// # Example\n///\n/// ```\n/// use landlock::{ABI, Access, Scope, BitFlags, make_bitflags};\n///\n/// let signal = Scope::Signal;\n///\n/// let signal_set: BitFlags<Scope> = signal.into();\n///\n/// let signal_uds = make_bitflags!(Scope::{Signal | AbstractUnixSocket});\n///\n/// let scope_v6 = Scope::from_all(ABI::V6);\n///\n/// assert_eq!(signal_uds, scope_v6);\n/// ```\n///\n/// # Warning\n///\n/// To avoid unknown restrictions **don't use `BitFlags::<Scope>::all()` nor `BitFlags::ALL`**,\n/// but use a version you tested and vetted instead,\n/// for instance [`Scope::from_all(ABI::V6)`](Access::from_all).\n/// Direct use of **the [`BitFlags`] API is deprecated**.\n/// See [`ABI`] for the rationale and help to test it.\n#[bitflags]\n#[repr(u64)]\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub enum Scope {\n    /// Restrict from connecting to abstract UNIX sockets created outside the sandbox.\n    AbstractUnixSocket = uapi::LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET as u64,\n    /// Restrict from sending signals to processes outside the sandbox.\n    Signal = uapi::LANDLOCK_SCOPE_SIGNAL as u64,\n}\n\n/// # Warning\n///\n/// If `ABI <= ABI::V5`, `Scope::from_all()` returns an empty `BitFlags<AccessScope>`, which\n/// makes `Ruleset::handle_access(AccessScope::from_all(ABI::V5))` return an error.\nimpl Access for Scope {\n    fn from_all(abi: ABI) -> BitFlags<Self> {\n        match abi {\n            ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 => BitFlags::EMPTY,\n            ABI::V6 => Scope::AbstractUnixSocket | Scope::Signal,\n        }\n    }\n}\n"
  },
  {
    "path": "src/uapi/bindgen.sh",
    "content": "#!/usr/bin/env bash\n# SPDX-License-Identifier: Apache-2.0 OR MIT\n\nset -u -e -o pipefail\n\nif [[ $# -ne 1 ]]; then\n\techo \"usage $(basename -- \"${BASH_SOURCE[0]}\") <kernel-source>\" >&2\n\texit 1\nfi\n\nHEADER=\"$(readlink -f -- \"$1\")/include/uapi/linux/landlock.h\"\n\nif [[ ! -f \"${HEADER}\" ]]; then\n\techo \"File not found: ${HEADER}\" >&2\n\texit 1\nfi\n\ncd \"$(dirname \"${BASH_SOURCE[0]}\")\"\n\nMSRV=\"$(sed -n 's/^rust-version = \"\\(.*\\)\"/\\1/p' ../../Cargo.toml)\"\n\nbindgen_landlock() {\n\tlocal arch=\"$1\"\n\tlocal output=\"$2\"\n\tshift 2\n\n\tbindgen \\\n\t\t\"$@\" \\\n\t\t--rust-target \"${MSRV}\" \\\n\t\t--allowlist-type \"landlock_.*\" \\\n\t\t--allowlist-var \"LANDLOCK_.*\" \\\n\t\t--no-doc-comments \\\n\t\t--no-derive-default \\\n\t\t--output \"${output}\" \\\n\t\t\"${HEADER}\" \\\n\t\t-- \\\n\t\t--target=\"${arch}-linux-gnu\"\n}\n\nfor ARCH in x86_64 i686; do\n\techo \"Generating bindings with tests for ${ARCH}.\"\n\tbindgen_landlock \"${ARCH}\" \"landlock_${ARCH}.rs\"\ndone\n\n# The Landlock ABI is architecture-agnostic (except for std::os::raw and memory\n# alignment).\necho \"Generating bindings without tests.\"\nbindgen_landlock x86_64 \"landlock_all.rs\" --no-layout-tests\n"
  },
  {
    "path": "src/uapi/landlock_all.rs",
    "content": "/* automatically generated by rust-bindgen 0.72.1 */\n\npub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;\npub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2;\npub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1;\npub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2;\npub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4;\npub const LANDLOCK_ACCESS_FS_EXECUTE: u32 = 1;\npub const LANDLOCK_ACCESS_FS_WRITE_FILE: u32 = 2;\npub const LANDLOCK_ACCESS_FS_READ_FILE: u32 = 4;\npub const LANDLOCK_ACCESS_FS_READ_DIR: u32 = 8;\npub const LANDLOCK_ACCESS_FS_REMOVE_DIR: u32 = 16;\npub const LANDLOCK_ACCESS_FS_REMOVE_FILE: u32 = 32;\npub const LANDLOCK_ACCESS_FS_MAKE_CHAR: u32 = 64;\npub const LANDLOCK_ACCESS_FS_MAKE_DIR: u32 = 128;\npub const LANDLOCK_ACCESS_FS_MAKE_REG: u32 = 256;\npub const LANDLOCK_ACCESS_FS_MAKE_SOCK: u32 = 512;\npub const LANDLOCK_ACCESS_FS_MAKE_FIFO: u32 = 1024;\npub const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u32 = 2048;\npub const LANDLOCK_ACCESS_FS_MAKE_SYM: u32 = 4096;\npub const LANDLOCK_ACCESS_FS_REFER: u32 = 8192;\npub const LANDLOCK_ACCESS_FS_TRUNCATE: u32 = 16384;\npub const LANDLOCK_ACCESS_FS_IOCTL_DEV: u32 = 32768;\npub const LANDLOCK_ACCESS_NET_BIND_TCP: u32 = 1;\npub const LANDLOCK_ACCESS_NET_CONNECT_TCP: u32 = 2;\npub const LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: u32 = 1;\npub const LANDLOCK_SCOPE_SIGNAL: u32 = 2;\npub type __s32 = ::std::os::raw::c_int;\npub type __u64 = ::std::os::raw::c_ulonglong;\n#[repr(C)]\n#[derive(Debug, Copy, Clone)]\npub struct landlock_ruleset_attr {\n    pub handled_access_fs: __u64,\n    pub handled_access_net: __u64,\n    pub scoped: __u64,\n}\npub const landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH: landlock_rule_type = 1;\npub const landlock_rule_type_LANDLOCK_RULE_NET_PORT: landlock_rule_type = 2;\npub type landlock_rule_type = ::std::os::raw::c_uint;\n#[repr(C, packed)]\n#[derive(Debug, Copy, Clone)]\npub struct landlock_path_beneath_attr {\n    pub allowed_access: __u64,\n    pub parent_fd: __s32,\n}\n#[repr(C)]\n#[derive(Debug, Copy, Clone)]\npub struct landlock_net_port_attr {\n    pub allowed_access: __u64,\n    pub port: __u64,\n}\n"
  },
  {
    "path": "src/uapi/landlock_i686.rs",
    "content": "/* automatically generated by rust-bindgen 0.72.1 */\n\npub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;\npub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2;\npub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1;\npub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2;\npub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4;\npub const LANDLOCK_ACCESS_FS_EXECUTE: u32 = 1;\npub const LANDLOCK_ACCESS_FS_WRITE_FILE: u32 = 2;\npub const LANDLOCK_ACCESS_FS_READ_FILE: u32 = 4;\npub const LANDLOCK_ACCESS_FS_READ_DIR: u32 = 8;\npub const LANDLOCK_ACCESS_FS_REMOVE_DIR: u32 = 16;\npub const LANDLOCK_ACCESS_FS_REMOVE_FILE: u32 = 32;\npub const LANDLOCK_ACCESS_FS_MAKE_CHAR: u32 = 64;\npub const LANDLOCK_ACCESS_FS_MAKE_DIR: u32 = 128;\npub const LANDLOCK_ACCESS_FS_MAKE_REG: u32 = 256;\npub const LANDLOCK_ACCESS_FS_MAKE_SOCK: u32 = 512;\npub const LANDLOCK_ACCESS_FS_MAKE_FIFO: u32 = 1024;\npub const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u32 = 2048;\npub const LANDLOCK_ACCESS_FS_MAKE_SYM: u32 = 4096;\npub const LANDLOCK_ACCESS_FS_REFER: u32 = 8192;\npub const LANDLOCK_ACCESS_FS_TRUNCATE: u32 = 16384;\npub const LANDLOCK_ACCESS_FS_IOCTL_DEV: u32 = 32768;\npub const LANDLOCK_ACCESS_NET_BIND_TCP: u32 = 1;\npub const LANDLOCK_ACCESS_NET_CONNECT_TCP: u32 = 2;\npub const LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: u32 = 1;\npub const LANDLOCK_SCOPE_SIGNAL: u32 = 2;\npub type __s32 = ::std::os::raw::c_int;\npub type __u64 = ::std::os::raw::c_ulonglong;\n#[repr(C)]\n#[derive(Debug, Copy, Clone)]\npub struct landlock_ruleset_attr {\n    pub handled_access_fs: __u64,\n    pub handled_access_net: __u64,\n    pub scoped: __u64,\n}\n#[test]\nfn bindgen_test_layout_landlock_ruleset_attr() {\n    const UNINIT: ::std::mem::MaybeUninit<landlock_ruleset_attr> =\n        ::std::mem::MaybeUninit::uninit();\n    let ptr = UNINIT.as_ptr();\n    assert_eq!(\n        ::std::mem::size_of::<landlock_ruleset_attr>(),\n        24usize,\n        \"Size of landlock_ruleset_attr\"\n    );\n    assert_eq!(\n        ::std::mem::align_of::<landlock_ruleset_attr>(),\n        4usize,\n        \"Alignment of landlock_ruleset_attr\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).handled_access_fs) as usize - ptr as usize },\n        0usize,\n        \"Offset of field: landlock_ruleset_attr::handled_access_fs\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).handled_access_net) as usize - ptr as usize },\n        8usize,\n        \"Offset of field: landlock_ruleset_attr::handled_access_net\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).scoped) as usize - ptr as usize },\n        16usize,\n        \"Offset of field: landlock_ruleset_attr::scoped\"\n    );\n}\npub const landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH: landlock_rule_type = 1;\npub const landlock_rule_type_LANDLOCK_RULE_NET_PORT: landlock_rule_type = 2;\npub type landlock_rule_type = ::std::os::raw::c_uint;\n#[repr(C, packed)]\n#[derive(Debug, Copy, Clone)]\npub struct landlock_path_beneath_attr {\n    pub allowed_access: __u64,\n    pub parent_fd: __s32,\n}\n#[test]\nfn bindgen_test_layout_landlock_path_beneath_attr() {\n    const UNINIT: ::std::mem::MaybeUninit<landlock_path_beneath_attr> =\n        ::std::mem::MaybeUninit::uninit();\n    let ptr = UNINIT.as_ptr();\n    assert_eq!(\n        ::std::mem::size_of::<landlock_path_beneath_attr>(),\n        12usize,\n        \"Size of landlock_path_beneath_attr\"\n    );\n    assert_eq!(\n        ::std::mem::align_of::<landlock_path_beneath_attr>(),\n        1usize,\n        \"Alignment of landlock_path_beneath_attr\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).allowed_access) as usize - ptr as usize },\n        0usize,\n        \"Offset of field: landlock_path_beneath_attr::allowed_access\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).parent_fd) as usize - ptr as usize },\n        8usize,\n        \"Offset of field: landlock_path_beneath_attr::parent_fd\"\n    );\n}\n#[repr(C)]\n#[derive(Debug, Copy, Clone)]\npub struct landlock_net_port_attr {\n    pub allowed_access: __u64,\n    pub port: __u64,\n}\n#[test]\nfn bindgen_test_layout_landlock_net_port_attr() {\n    const UNINIT: ::std::mem::MaybeUninit<landlock_net_port_attr> =\n        ::std::mem::MaybeUninit::uninit();\n    let ptr = UNINIT.as_ptr();\n    assert_eq!(\n        ::std::mem::size_of::<landlock_net_port_attr>(),\n        16usize,\n        \"Size of landlock_net_port_attr\"\n    );\n    assert_eq!(\n        ::std::mem::align_of::<landlock_net_port_attr>(),\n        4usize,\n        \"Alignment of landlock_net_port_attr\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).allowed_access) as usize - ptr as usize },\n        0usize,\n        \"Offset of field: landlock_net_port_attr::allowed_access\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).port) as usize - ptr as usize },\n        8usize,\n        \"Offset of field: landlock_net_port_attr::port\"\n    );\n}\n"
  },
  {
    "path": "src/uapi/landlock_x86_64.rs",
    "content": "/* automatically generated by rust-bindgen 0.72.1 */\n\npub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;\npub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2;\npub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1;\npub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2;\npub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4;\npub const LANDLOCK_ACCESS_FS_EXECUTE: u32 = 1;\npub const LANDLOCK_ACCESS_FS_WRITE_FILE: u32 = 2;\npub const LANDLOCK_ACCESS_FS_READ_FILE: u32 = 4;\npub const LANDLOCK_ACCESS_FS_READ_DIR: u32 = 8;\npub const LANDLOCK_ACCESS_FS_REMOVE_DIR: u32 = 16;\npub const LANDLOCK_ACCESS_FS_REMOVE_FILE: u32 = 32;\npub const LANDLOCK_ACCESS_FS_MAKE_CHAR: u32 = 64;\npub const LANDLOCK_ACCESS_FS_MAKE_DIR: u32 = 128;\npub const LANDLOCK_ACCESS_FS_MAKE_REG: u32 = 256;\npub const LANDLOCK_ACCESS_FS_MAKE_SOCK: u32 = 512;\npub const LANDLOCK_ACCESS_FS_MAKE_FIFO: u32 = 1024;\npub const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u32 = 2048;\npub const LANDLOCK_ACCESS_FS_MAKE_SYM: u32 = 4096;\npub const LANDLOCK_ACCESS_FS_REFER: u32 = 8192;\npub const LANDLOCK_ACCESS_FS_TRUNCATE: u32 = 16384;\npub const LANDLOCK_ACCESS_FS_IOCTL_DEV: u32 = 32768;\npub const LANDLOCK_ACCESS_NET_BIND_TCP: u32 = 1;\npub const LANDLOCK_ACCESS_NET_CONNECT_TCP: u32 = 2;\npub const LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: u32 = 1;\npub const LANDLOCK_SCOPE_SIGNAL: u32 = 2;\npub type __s32 = ::std::os::raw::c_int;\npub type __u64 = ::std::os::raw::c_ulonglong;\n#[repr(C)]\n#[derive(Debug, Copy, Clone)]\npub struct landlock_ruleset_attr {\n    pub handled_access_fs: __u64,\n    pub handled_access_net: __u64,\n    pub scoped: __u64,\n}\n#[test]\nfn bindgen_test_layout_landlock_ruleset_attr() {\n    const UNINIT: ::std::mem::MaybeUninit<landlock_ruleset_attr> =\n        ::std::mem::MaybeUninit::uninit();\n    let ptr = UNINIT.as_ptr();\n    assert_eq!(\n        ::std::mem::size_of::<landlock_ruleset_attr>(),\n        24usize,\n        \"Size of landlock_ruleset_attr\"\n    );\n    assert_eq!(\n        ::std::mem::align_of::<landlock_ruleset_attr>(),\n        8usize,\n        \"Alignment of landlock_ruleset_attr\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).handled_access_fs) as usize - ptr as usize },\n        0usize,\n        \"Offset of field: landlock_ruleset_attr::handled_access_fs\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).handled_access_net) as usize - ptr as usize },\n        8usize,\n        \"Offset of field: landlock_ruleset_attr::handled_access_net\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).scoped) as usize - ptr as usize },\n        16usize,\n        \"Offset of field: landlock_ruleset_attr::scoped\"\n    );\n}\npub const landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH: landlock_rule_type = 1;\npub const landlock_rule_type_LANDLOCK_RULE_NET_PORT: landlock_rule_type = 2;\npub type landlock_rule_type = ::std::os::raw::c_uint;\n#[repr(C, packed)]\n#[derive(Debug, Copy, Clone)]\npub struct landlock_path_beneath_attr {\n    pub allowed_access: __u64,\n    pub parent_fd: __s32,\n}\n#[test]\nfn bindgen_test_layout_landlock_path_beneath_attr() {\n    const UNINIT: ::std::mem::MaybeUninit<landlock_path_beneath_attr> =\n        ::std::mem::MaybeUninit::uninit();\n    let ptr = UNINIT.as_ptr();\n    assert_eq!(\n        ::std::mem::size_of::<landlock_path_beneath_attr>(),\n        12usize,\n        \"Size of landlock_path_beneath_attr\"\n    );\n    assert_eq!(\n        ::std::mem::align_of::<landlock_path_beneath_attr>(),\n        1usize,\n        \"Alignment of landlock_path_beneath_attr\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).allowed_access) as usize - ptr as usize },\n        0usize,\n        \"Offset of field: landlock_path_beneath_attr::allowed_access\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).parent_fd) as usize - ptr as usize },\n        8usize,\n        \"Offset of field: landlock_path_beneath_attr::parent_fd\"\n    );\n}\n#[repr(C)]\n#[derive(Debug, Copy, Clone)]\npub struct landlock_net_port_attr {\n    pub allowed_access: __u64,\n    pub port: __u64,\n}\n#[test]\nfn bindgen_test_layout_landlock_net_port_attr() {\n    const UNINIT: ::std::mem::MaybeUninit<landlock_net_port_attr> =\n        ::std::mem::MaybeUninit::uninit();\n    let ptr = UNINIT.as_ptr();\n    assert_eq!(\n        ::std::mem::size_of::<landlock_net_port_attr>(),\n        16usize,\n        \"Size of landlock_net_port_attr\"\n    );\n    assert_eq!(\n        ::std::mem::align_of::<landlock_net_port_attr>(),\n        8usize,\n        \"Alignment of landlock_net_port_attr\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).allowed_access) as usize - ptr as usize },\n        0usize,\n        \"Offset of field: landlock_net_port_attr::allowed_access\"\n    );\n    assert_eq!(\n        unsafe { ::std::ptr::addr_of!((*ptr).port) as usize - ptr as usize },\n        8usize,\n        \"Offset of field: landlock_net_port_attr::port\"\n    );\n}\n"
  },
  {
    "path": "src/uapi/mod.rs",
    "content": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// Use architecture-specific bindings for native x86_64 and x86 architectures.\n// They contain minimal Landlock-only bindings with layout tests.\n#[allow(dead_code)]\n#[allow(non_camel_case_types)]\n#[allow(non_snake_case)]\n#[allow(non_upper_case_globals)]\n#[cfg(target_arch = \"x86_64\")]\n#[path = \"landlock_x86_64.rs\"]\nmod landlock;\n\n#[allow(dead_code)]\n#[allow(non_camel_case_types)]\n#[allow(non_snake_case)]\n#[allow(non_upper_case_globals)]\n#[cfg(target_arch = \"x86\")]\n#[path = \"landlock_i686.rs\"]\nmod landlock;\n\n// For all other architectures, use the architecture-agnostic landlock_all.rs\n// bindings without layout tests.\n#[allow(dead_code)]\n#[allow(non_camel_case_types)]\n#[allow(non_snake_case)]\n#[allow(non_upper_case_globals)]\n#[cfg(not(any(target_arch = \"x86_64\", target_arch = \"x86\")))]\n#[path = \"landlock_all.rs\"]\nmod landlock;\n\n#[rustfmt::skip]\npub use self::landlock::{\n    landlock_net_port_attr,\n    landlock_path_beneath_attr,\n    landlock_rule_type,\n    landlock_rule_type_LANDLOCK_RULE_NET_PORT,\n    landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH,\n    landlock_ruleset_attr,\n    LANDLOCK_ACCESS_FS_EXECUTE,\n    LANDLOCK_ACCESS_FS_WRITE_FILE,\n    LANDLOCK_ACCESS_FS_READ_FILE,\n    LANDLOCK_ACCESS_FS_READ_DIR,\n    LANDLOCK_ACCESS_FS_REMOVE_DIR,\n    LANDLOCK_ACCESS_FS_REMOVE_FILE,\n    LANDLOCK_ACCESS_FS_MAKE_CHAR,\n    LANDLOCK_ACCESS_FS_MAKE_DIR,\n    LANDLOCK_ACCESS_FS_MAKE_REG,\n    LANDLOCK_ACCESS_FS_MAKE_SOCK,\n    LANDLOCK_ACCESS_FS_MAKE_FIFO,\n    LANDLOCK_ACCESS_FS_MAKE_BLOCK,\n    LANDLOCK_ACCESS_FS_MAKE_SYM,\n    LANDLOCK_ACCESS_FS_REFER,\n    LANDLOCK_ACCESS_FS_TRUNCATE,\n    LANDLOCK_ACCESS_FS_IOCTL_DEV,\n    LANDLOCK_ACCESS_NET_BIND_TCP,\n    LANDLOCK_ACCESS_NET_CONNECT_TCP,\n    LANDLOCK_CREATE_RULESET_VERSION,\n    LANDLOCK_CREATE_RULESET_ERRATA,\n    LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,\n    LANDLOCK_SCOPE_SIGNAL,\n};\n\nuse libc::{\n    __u32, c_int, c_void, size_t, syscall, SYS_landlock_add_rule, SYS_landlock_create_ruleset,\n    SYS_landlock_restrict_self,\n};\n\n#[rustfmt::skip]\npub unsafe fn landlock_create_ruleset(attr: *const landlock_ruleset_attr, size: size_t,\n                                      flags: __u32) -> c_int {\n    syscall(SYS_landlock_create_ruleset, attr, size, flags) as c_int\n}\n\n#[rustfmt::skip]\npub unsafe fn landlock_add_rule(ruleset_fd: c_int, rule_type: landlock_rule_type,\n                                rule_attr: *const c_void, flags: __u32) -> c_int {\n    syscall(SYS_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags) as c_int\n}\n\npub unsafe fn landlock_restrict_self(ruleset_fd: c_int, flags: __u32) -> c_int {\n    syscall(SYS_landlock_restrict_self, ruleset_fd, flags) as c_int\n}\n"
  }
]