Full Code of landlock-lsm/rust-landlock for AI

main 1e3a00440333 cached
25 files
203.2 KB
51.0k tokens
314 symbols
1 requests
Download .txt
Showing preview only (212K chars total). Download the full file or copy to clipboard to get everything.
Repository: landlock-lsm/rust-landlock
Branch: main
Commit: 1e3a00440333
Files: 25
Total size: 203.2 KB

Directory structure:
gitextract_ih3s9dw9/

├── .github/
│   └── workflows/
│       ├── pages.yml
│       └── rust.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── COPYRIGHT
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── examples/
│   └── sandboxer.rs
└── src/
    ├── access.rs
    ├── compat.rs
    ├── errata.rs
    ├── errors.rs
    ├── fs.rs
    ├── lib.rs
    ├── net.rs
    ├── ruleset.rs
    ├── scope.rs
    └── uapi/
        ├── bindgen.sh
        ├── landlock_all.rs
        ├── landlock_i686.rs
        ├── landlock_x86_64.rs
        └── mod.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/pages.yml
================================================
name: GitHub Pages

on:
  push:
    branches: [ main ]

  workflow_dispatch:

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    if: github.repository == 'landlock-lsm/rust-landlock'
    runs-on: ubuntu-24.04

    env:
      CARGO_TERM_COLOR: always

    permissions:
      contents: read
      id-token: write

    steps:
    - uses: actions/checkout@v3

    - name: Install Rust stable
      run: |
        rm ~/.cargo/bin/{cargo-fmt,rustfmt} || :
        rustup default stable
        rustup update

    - name: Build documentation
      run: rustup run stable cargo doc --no-deps

    - name: Add index
      run: |
        echo '<meta http-equiv="refresh" content="0; url=landlock">' > target/doc/index.html

    - name: Upload artifact
      uses: actions/upload-pages-artifact@v3
      with:
        path: target/doc

  deploy:
    needs: build
    runs-on: ubuntu-24.04
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}

    permissions:
      pages: write
      id-token: write

    steps:
      - name: Deploy to GitHub Pages
        uses: actions/deploy-pages@v4


================================================
FILE: .github/workflows/rust.yml
================================================
name: Rust checks

permissions: {}

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  CARGO_TERM_COLOR: always
  RUSTDOCFLAGS: -D warnings
  RUSTFLAGS: -D warnings
  LANDLOCK_TEST_TOOLS_COMMIT: fad769c39b42183fb2a2e1263fe00dfa5b9f2bda

# Ubuntu versions: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources

jobs:
  commit_list:
    runs-on: ubuntu-24.04
    steps:

    - uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Get commit list (push)
      id: get_commit_list_push
      if: ${{ github.event_name == 'push' }}
      run: |
        echo "id0=$GITHUB_SHA" > $GITHUB_OUTPUT
        echo "List of tested commits:" > $GITHUB_STEP_SUMMARY
        sed -n 's,^id[0-9]\+=\(.*\),- https://github.com/landlock-lsm/rust-landlock/commit/\1,p' -- $GITHUB_OUTPUT >> $GITHUB_STEP_SUMMARY

    - name: Get commit list (PR)
      id: get_commit_list_pr
      if: ${{ github.event_name == 'pull_request' }}
      run: |
        git rev-list --reverse refs/remotes/origin/${{ github.base_ref }}..${{ github.event.pull_request.head.sha }} | awk '{ print "id" NR "=" $1 }' > $GITHUB_OUTPUT
        git diff --quiet ${{ github.event.pull_request.head.sha }} ${{ github.sha }} || echo "id0=$GITHUB_SHA" >> $GITHUB_OUTPUT
        echo "List of tested commits:" > $GITHUB_STEP_SUMMARY
        sed -n 's,^id[0-9]\+=\(.*\),- https://github.com/landlock-lsm/rust-landlock/commit/\1,p' -- $GITHUB_OUTPUT >> $GITHUB_STEP_SUMMARY

    outputs:
      commits: ${{ toJSON(steps.*.outputs.*) }}

  kernel_list:
    runs-on: ubuntu-24.04
    outputs:
      kernels: ${{ toJSON(steps.id.outputs.*) }}
    steps:

    - name: Identify latest Linux versions
      id: id
      run: |
        echo "List of tested kernels:" > $GITHUB_STEP_SUMMARY
        abi=0
        for version in 5.10 5.15 6.1 6.4 6.7 6.10 6.12; do
          commit="$(git ls-remote https://github.com/landlock-lsm/linux refs/heads/linux-${version}.y | awk 'NR == 1 { print $1 }')"
          if [[ -z "${commit}" ]]; then
            echo "ERROR: Failed to fetch Linux ${version}" >&2
            exit 1
          fi
          echo "kernel_${abi}={ \"version\":\"${version}\",  \"abi\":\"${abi}\",  \"commit\":\"${commit}\" }" >> $GITHUB_OUTPUT
          echo "- Linux ${version}.y with Landlock ABI ${abi}: https://github.com/landlock-lsm/linux/commit/${commit}" >> $GITHUB_STEP_SUMMARY
          let abi++ || :
        done

  get_kernels:
    runs-on: ubuntu-24.04
    needs: kernel_list
    strategy:
      fail-fast: false
      matrix:
        kernel: ${{ fromJSON(needs.kernel_list.outputs.kernels) }}
    steps:

    - name: Cache Linux ${{ fromJSON(matrix.kernel).version}}.y
      id: cache_linux
      uses: actions/cache@v4
      with:
        path: linux-${{ fromJSON(matrix.kernel).version }}
        key: linux-${{ fromJSON(matrix.kernel).commit }}-${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}

    - name: Clone Landlock test tools
      if: steps.cache_linux.outputs.cache-hit != 'true'
      uses: actions/checkout@v4
      with:
        repository: landlock-lsm/landlock-test-tools
        ref: ${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}
        path: landlock-test-tools

    - name: Clone Linux ${{ fromJSON(matrix.kernel).version}}.y
      if: steps.cache_linux.outputs.cache-hit != 'true'
      uses: actions/checkout@v4
      with:
        repository: landlock-lsm/linux
        ref: ${{ fromJSON(matrix.kernel).commit }}
        path: linux

    - name: Build Linux ${{ fromJSON(matrix.kernel).version}}.y
      if: steps.cache_linux.outputs.cache-hit != 'true'
      working-directory: linux
      run: |
        O=. ../landlock-test-tools/check-linux.sh build_light
        mv linux ../linux-${{ fromJSON(matrix.kernel).version}}

  ubuntu_24_rust_msrv:
    runs-on: ubuntu-24.04
    needs: commit_list
    strategy:
      fail-fast: false
      matrix:
        commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}
    steps:

    - uses: actions/checkout@v4
      with:
        ref: ${{ matrix.commit }}

    - name: Clone Landlock test tools
      uses: actions/checkout@v4
      with:
        repository: landlock-lsm/landlock-test-tools
        ref: ${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}
        path: landlock-test-tools

    - name: Get MSRV
      run: sed -n 's/^rust-version = "\([0-9.]\+\)"$/RUST_TOOLCHAIN=\1/p' Cargo.toml >> $GITHUB_ENV

    - name: Install 32-bit development libraries
      run: |
        sudo apt update
        sudo apt install gcc-multilib libc6-dev-i386

    - name: Install Rust MSRV
      run: |
        rm ~/.cargo/bin/{cargo-fmt,rustfmt} || :
        rustup self update
        rustup default ${{ env.RUST_TOOLCHAIN }}
        rustup update ${{ env.RUST_TOOLCHAIN }}
        rustup target add i686-unknown-linux-gnu
        rustup target add x86_64-unknown-linux-gnu

    - name: Build (x86_64)
      run: rustup run ${{ env.RUST_TOOLCHAIN }} cargo build --target x86_64-unknown-linux-gnu --verbose

    - name: Build (x86)
      run: rustup run ${{ env.RUST_TOOLCHAIN }} cargo build --target i686-unknown-linux-gnu --verbose

    - name: Run tests against the local kernel (Landlock ABI ${{ env.LANDLOCK_CRATE_TEST_ABI }} on x86_64)
      run: rustup run ${{ env.RUST_TOOLCHAIN }} cargo test --target x86_64-unknown-linux-gnu --verbose

    - name: Run tests against the local kernel (Landlock ABI ${{ env.LANDLOCK_CRATE_TEST_ABI }} on x86)
      run: rustup run ${{ env.RUST_TOOLCHAIN }} cargo test --target i686-unknown-linux-gnu --verbose

    - name: Run tests against Linux 6.1
      run: CARGO="rustup run ${{ env.RUST_TOOLCHAIN }} cargo" ./landlock-test-tools/test-rust.sh linux-6.1 2

  ubuntu_22_rust_stable:
    runs-on: ubuntu-22.04
    needs: commit_list
    strategy:
      fail-fast: false
      matrix:
        commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}
    env:
      LANDLOCK_CRATE_TEST_ABI: 4
    steps:

    - name: Install Rust stable
      run: |
        rm ~/.cargo/bin/{cargo-fmt,rustfmt} || :
        rustup self update
        rustup default stable
        rustup component add rustfmt clippy
        rustup update

    - uses: actions/checkout@v4
      with:
        ref: ${{ matrix.commit }}

    - name: Build
      run: rustup run stable cargo build --verbose

    - name: Run tests against the local kernel (Landlock ABI ${{ env.LANDLOCK_CRATE_TEST_ABI }})
      run: rustup run stable cargo test --verbose

    - name: Check format
      run: rustup run stable cargo fmt --all -- --check

    - name: Check source with Clippy
      run: rustup run stable cargo clippy -- --deny warnings

    - name: Check tests with Clippy
      run: rustup run stable cargo clippy --tests -- --deny warnings

    - name: Check documentation
      run: rustup run stable cargo doc --no-deps

  ubuntu_24_rust_stable:
    runs-on: ubuntu-24.04
    needs: [commit_list, kernel_list, get_kernels]
    strategy:
      fail-fast: false
      matrix:
        commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}
        kernel: ${{ fromJSON(needs.kernel_list.outputs.kernels) }}
    env:
      LANDLOCK_CRATE_TEST_ABI: 4
      # $CARGO is used by landlock-test-tools/test-rust.sh
      CARGO: rustup run stable cargo
    steps:

    - name: Install Rust stable
      run: |
        rm ~/.cargo/bin/{cargo-fmt,rustfmt} || :
        rustup self update
        rustup default stable
        rustup update

    - name: Clone Landlock test tools
      uses: actions/checkout@v4
      with:
        repository: landlock-lsm/landlock-test-tools
        ref: ${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}
        path: landlock-test-tools

    - name: Clone rust-landlock
      uses: actions/checkout@v4
      with:
        ref: ${{ matrix.commit }}
        path: rust-landlock

    - name: Get Linux ${{ fromJSON(matrix.kernel).version}}.y
      uses: actions/cache/restore@v4
      with:
        path: linux-${{ fromJSON(matrix.kernel).version }}
        key: linux-${{ fromJSON(matrix.kernel).commit }}-${{ env.LANDLOCK_TEST_TOOLS_COMMIT }}
        fail-on-cache-miss: true

    - name: Run tests against Linux ${{ fromJSON(matrix.kernel).version }}.y
      working-directory: rust-landlock
      run: ../landlock-test-tools/test-rust.sh ../linux-${{ fromJSON(matrix.kernel).version }} ${{ fromJSON(matrix.kernel).abi }}


================================================
FILE: .gitignore
================================================
/Cargo.lock
/target


================================================
FILE: CHANGELOG.md
================================================
# Landlock changelog

## [v0.4.4](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.4)

### New API

- Added support for all architectures ([PR #111](https://github.com/landlock-lsm/rust-landlock/pull/111)).
- 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)).

### Dependencies

- Bumped MSRV to Rust 1.68 ([PR #112](https://github.com/landlock-lsm/rust-landlock/pull/112)).

### Testing

- Extended CI to build and test on i686 architecture ([PR #111](https://github.com/landlock-lsm/rust-landlock/pull/111)).

### Example

- Enhanced sandboxer example to print helpful hints about Landlock status ([PR #103](https://github.com/landlock-lsm/rust-landlock/pull/103)).

## [v0.4.3](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.3)

### New API

- Implemented common traits (e.g., `Debug`) for public types ([PR #108](https://github.com/landlock-lsm/rust-landlock/pull/108)).

### Documentation

- Extended [CONTRIBUTING.md](CONTRIBUTING.md) documentation with additional testing and development guidelines ([PR #95](https://github.com/landlock-lsm/rust-landlock/pull/95)).
- 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)).

### Testing

- Added test case for `AccessFs::from_file()` method ([PR #92](https://github.com/landlock-lsm/rust-landlock/pull/92)).

## [v0.4.2](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.2)

### New API

- 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)).
- Added `From<RulesetCreated>` implementation for `Option<OwnedFd>` ([PR #104](https://github.com/landlock-lsm/rust-landlock/pull/104))
- 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)).
- 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)).
- 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)).

### Documentation

- Added clarifying notes about `AccessFs::WriteFile` behavior and `path_beneath_rules` usage ([PR #80](https://github.com/landlock-lsm/rust-landlock/pull/80)).
- Introduced [CONTRIBUTING.md](CONTRIBUTING.md) with testing workflow explanations ([PR #76](https://github.com/landlock-lsm/rust-landlock/pull/76)).

### Testing

- Enhanced test coverage for new API and added testing against Linux 6.12 ([PR #96](https://github.com/landlock-lsm/rust-landlock/pull/96)).
- 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)).
- 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)).

### Example

- 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.

## [v0.4.1](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.1)

### New API

Add 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)).

### Testing

Improved the CI to better test against different kernel versions ([PR #72](https://github.com/landlock-lsm/rust-landlock/pull/72)).


## [v0.4.0](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.0)

### New API

Add support for Landlock ABI 4: control TCP binding and connection according to specified network ports.
This is now possible with the [`AccessNet`](https://landlock.io/rust-landlock/landlock/enum.AccessNet.html) rights and
the [`NetPort`](https://landlock.io/rust-landlock/landlock/struct.NetPort.html) rule
([PR #55](https://github.com/landlock-lsm/rust-landlock/pull/55)).

### Breaking change

The `from_read()` and `from_write()` methods moved from the `Access` trait to the `AccessFs` struct
([commit 68f066eba571](https://github.com/landlock-lsm/rust-landlock/commit/68f066eba571c1f9212f5a07016aac9ffb0d1c27)).

### Compatibility management

Improve compatibility consistency and prioritize runtime errors against compatibility errors
([PR #67](https://github.com/landlock-lsm/rust-landlock/pull/67)).

Fixed 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`).
When trying to enforce this ruleset, this led to a runtime error (i.e. wrong file descriptor) instead of a compatibility error.

To simplify compatibility management, always call `prctl(PR_SET_NO_NEW_PRIVS, 1)` by default (see `set_no_new_privs()`).
This 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
([commit d99f75155bec](https://github.com/landlock-lsm/rust-landlock/commit/d99f75155bec2040cf4ce1532007cd3b8a23e2fb)).


## [v0.3.1](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.3.1)

### New API

Add [`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)).


## [v0.3.0](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.3.0)

### New API

Add support for Landlock ABI 3: control truncate operations with the new
[`AccessFs::Truncate`](https://landlock.io/rust-landlock/landlock/enum.AccessFs.html#variant.Truncate)
right ([PR #40](https://github.com/landlock-lsm/rust-landlock/pull/40)).

Revamp the compatibility handling and add a new
[`set_compatibility()`](https://landlock.io/rust-landlock/landlock/trait.Compatible.html#method.set_compatibility)
method for `Ruleset`, `RulesetCreated`, and `PathBeneath`.
We can now fine-tune the compatibility behavior according to the running kernel
and then the supported features thanks to three compatible levels:
best effort, soft requirement and hard requirement
([PR #12](https://github.com/landlock-lsm/rust-landlock/pull/12)).

Add a new [`AccessFs::from_file()`](https://landlock.io/rust-landlock/landlock/enum.AccessFs.html#method.from_file)
helper ([commit 0b3238c6dd70](https://github.com/landlock-lsm/rust-landlock/commit/0b3238c6dd70)).

### Deprecated API

Deprecate the [`set_best_effort()`](https://landlock.io/rust-landlock/landlock/trait.Compatible.html#method.set_best_effort)
method and replace it with `set_compatibility()`
([PR #12](https://github.com/landlock-lsm/rust-landlock/pull/12)).

Deprecate [`Ruleset::new()`](https://landlock.io/rust-landlock/landlock/struct.Ruleset.html#method.new)
and replace it with `Ruleset::default()`
([PR #44](https://github.com/landlock-lsm/rust-landlock/pull/44)).

### Breaking changes

We now check that a ruleset really handles at least one access right,
which can now cause `Ruleset::create()` to return an error if the ruleset compatibility level is
`HardRequirement` or `set_best_effort(false)`
([commit 95addc13b4a8](https://github.com/landlock-lsm/rust-landlock/commit/95addc13b4a8)).

We now check that access rights passed to `add_rule()` make sense according to the file type.
To handle most use cases,
`path_beneath_rules()` now automatically check and downgrade access rights for files
(i.e. remove superfluous directory-only access rights,
 [commit 8e47940b3722](https://github.com/landlock-lsm/rust-landlock/commit/8e47940b3722)).

### Testing

Test coverage in the CI is greatly improved by running all tests on all relevant kernel versions:
Linux 5.10, 5.15, 6.1, and 6.4
([PR #41](https://github.com/landlock-lsm/rust-landlock/pull/41)).

Run each test in a dedicated thread to avoid inconsistent behavior
([PR #46](https://github.com/landlock-lsm/rust-landlock/pull/46)).


## [v0.2.0](https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.2.0)

This is the first major release of this crate.
It brings a high-level interface to the Landlock kernel interface.


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

Thanks for your interest in contributing to rust-landlock!

## Testing vs kernel ABI

The Landlock functionality exposed differs between kernel versions. Based on
the Landlock ABI version of the running system, rust-landlock runs different
subsets of tests. For local development, running `cargo test` will test against
your currently running kernel version (and the Landlock ABI that it ships).
However, this may result in some tests being skipped.

To fully test a change, it should be verified against a range of ABI versions.
This is done by the Github Actions CI, but doing so locally is challenging.

Using the `LANDLOCK_CRATE_TEST_ABI` variable, the tested ABI version can be
overridden. For more details, take a look at the comment in
[`compat.rs:current_kernel_abi()`][current-kernel-abi].

For more information about Landlock ABIs, see: [enum ABI][enum-abi]

[current-kernel-abi]: https://github.com/landlock-lsm/rust-landlock/blob/main/src/compat.rs
[enum-abi]: https://landlock.io/rust-landlock/landlock/enum.ABI.html

## Licensing & DCO

rust-landlock is double-licensed under the terms of [Apache 2.0][apache-2.0]
and [MIT][mit].

All changes submitted to rust-landlock must be [signed off][dco].

[apache-2.0]: https://spdx.org/licenses/Apache-2.0.html
[mit]: https://spdx.org/licenses/MIT.html
[dco]: https://github.com/apps/dco


================================================
FILE: COPYRIGHT
================================================
Copyright 2020 Mickaël Salaün

Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
<LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
option. All files in the project carrying such notice may not be
copied, modified, or distributed except according to those terms.


================================================
FILE: Cargo.toml
================================================
[package]
name = "landlock"
version = "0.4.4"
edition = "2021"
rust-version = "1.71"
description = "Landlock LSM helpers"
homepage = "https://landlock.io"
repository = "https://github.com/landlock-lsm/rust-landlock"
license = "MIT OR Apache-2.0"
keywords = ["access-control", "linux", "sandbox", "security"]
categories = ["api-bindings", "os::linux-apis", "virtualization", "filesystem"]
exclude = [".gitignore"]
readme = "README.md"

[dependencies]
enumflags2 = "0.7"
libc = "0.2.175"
thiserror = "2.0"

[dev-dependencies]
anyhow = "1.0"
lazy_static = "1"
strum = "0.26"
strum_macros = "0.26"


================================================
FILE: LICENSE-APACHE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2020 Mickaël Salaün

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: LICENSE-MIT
================================================
MIT License

Copyright (c) 2020 Mickaël Salaün

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# Rust Landlock library

Landlock is a security feature available since Linux 5.13.
The 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.
This kind of sandbox is expected to help mitigate the security impact of bugs, unexpected or malicious behaviors in applications.
Landlock empowers any process, including unprivileged ones, to securely restrict themselves.
More information about Landlock can be found in the [official website](https://landlock.io).

This Rust crate provides a safe abstraction for the Landlock system calls along with some helpers.

## Use cases

This crate is especially useful to protect users' data by sandboxing:
* trusted applications dealing with potentially malicious data
  (e.g., complex file format, network request) that could exploit security vulnerabilities;
* sandbox managers, container runtimes or shells launching untrusted applications.

## Examples

A simple example can be found with the
[`path_beneath_rules()`](https://landlock.io/rust-landlock/landlock/fn.path_beneath_rules.html) helper.
More complex examples can be found with the
[`Ruleset` documentation](https://landlock.io/rust-landlock/landlock/struct.Ruleset.html)
and the [sandboxer example](examples/sandboxer.rs).

## [Crate documentation](https://landlock.io/rust-landlock/landlock/)

## Changelog

* [v0.4.4](CHANGELOG.md#v044)
* [v0.4.3](CHANGELOG.md#v043)
* [v0.4.2](CHANGELOG.md#v042)
* [v0.4.1](CHANGELOG.md#v041)
* [v0.4.0](CHANGELOG.md#v040)
* [v0.3.1](CHANGELOG.md#v031)
* [v0.3.0](CHANGELOG.md#v030)
* [v0.2.0](CHANGELOG.md#v020)


================================================
FILE: examples/sandboxer.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

// This is an idiomatic Rust rewrite of a C example:
// https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/samples/landlock/sandboxer.c

use anyhow::{anyhow, bail, Context};
use landlock::{
    path_beneath_rules, Access, AccessFs, AccessNet, BitFlags, LandlockStatus, NetPort,
    PathBeneath, PathFd, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus, Scope, ABI,
};
use std::env;
use std::ffi::OsStr;
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::os::unix::process::CommandExt;
use std::process::Command;

const ENV_FS_RO_NAME: &str = "LL_FS_RO";
const ENV_FS_RW_NAME: &str = "LL_FS_RW";
const ENV_TCP_BIND_NAME: &str = "LL_TCP_BIND";
const ENV_TCP_CONNECT_NAME: &str = "LL_TCP_CONNECT";
const ENV_SCOPED_NAME: &str = "LL_SCOPED";

struct PathEnv {
    paths: Vec<u8>,
    access: BitFlags<AccessFs>,
}

impl PathEnv {
    /// Create an object able to iterate PathBeneath rules
    ///
    /// # Arguments
    ///
    /// * `name`: String identifying an environment variable containing paths requested to be
    ///   allowed. Paths are separated with ":", e.g. "/bin:/lib:/usr:/proc". In case an empty
    ///   string is provided, NO restrictions are applied.
    /// * `access`: Set of access-rights allowed for each of the parsed paths.
    fn new<'a>(name: &'a str, access: BitFlags<AccessFs>) -> anyhow::Result<Self> {
        Ok(Self {
            paths: env::var_os(name)
                .ok_or(anyhow!("missing environment variable {name}"))?
                .into_vec(),
            access,
        })
    }

    fn iter(&self) -> impl Iterator<Item = anyhow::Result<PathBeneath<PathFd>>> + '_ {
        let is_empty = self.paths.is_empty();
        path_beneath_rules(
            self.paths
                .split(|b| *b == b':')
                // Skips the first empty element of an empty string.
                .skip_while(move |_| is_empty)
                .map(OsStr::from_bytes),
            self.access,
        )
        .map(|r| Ok(r?))
    }
}

struct PortEnv {
    ports: Vec<u8>,
    access: AccessNet,
}

impl PortEnv {
    fn new<'a>(name: &'a str, access: AccessNet) -> anyhow::Result<Self> {
        Ok(Self {
            ports: env::var_os(name).unwrap_or_default().into_vec(),
            access,
        })
    }

    fn iter(&self) -> impl Iterator<Item = anyhow::Result<NetPort>> + '_ {
        let is_empty = self.ports.is_empty();
        self.ports
            .split(|b| *b == b':')
            // Skips the first empty element of an empty string.
            .skip_while(move |_| is_empty)
            .map(OsStr::from_bytes)
            .map(|port| {
                let port = port
                    .to_str()
                    .context("failed to convert port string")?
                    .parse::<u16>()
                    .context("failed to convert port to 16-bit integer")?;
                Ok(NetPort::new(port, self.access))
            })
    }
}

fn main() -> anyhow::Result<()> {
    let mut args = env::args_os();
    let program_name = args
        .next()
        .context("Missing the sandboxer program name (i.e. argv[0])")?;

    let cmd_name = args.next().ok_or_else(|| {
        let program_name = program_name.to_string_lossy();
        eprintln!(
            "usage: {ENV_FS_RO_NAME}=\"...\" {ENV_FS_RW_NAME}=\"...\" [other environment variables] {program_name} <cmd> [args]...\n"
        );
        eprintln!("Execute the given command in a restricted environment.");
        eprintln!("Multi-valued settings (lists of ports, paths, scopes) are colon-delimited.\n");
        eprintln!("Mandatory settings:");
        eprintln!("* {ENV_FS_RO_NAME}: paths allowed to be used in a read-only way");
        eprintln!("* {ENV_FS_RW_NAME}: paths allowed to be used in a read-write way\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):");
        eprintln!("* {ENV_TCP_BIND_NAME}: ports allowed to bind (server)");
        eprintln!("* {ENV_TCP_CONNECT_NAME}: ports allowed to connect (client)");
        eprintln!("* {ENV_SCOPED_NAME}: actions denied on the outside of the Landlock domain:");
        eprintln!("  - \"a\" to restrict opening abstract unix sockets");
        eprintln!("  - \"s\" to restrict sending signals");
        eprintln!(
            "\nExample:\n\
                {ENV_FS_RO_NAME}=\"${{PATH}}:/lib:/usr:/proc:/etc:/dev/urandom\" \
                {ENV_FS_RW_NAME}=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" \
                {ENV_TCP_BIND_NAME}=\"9418\" \
                {ENV_TCP_CONNECT_NAME}=\"80:443\" \
                {ENV_SCOPED_NAME}=\"a:s\" \
                {program_name} bash -i\n"
        );
        anyhow!("Missing command")
    })?;

    let abi = ABI::V6;
    let mut ruleset = Ruleset::default().handle_access(AccessFs::from_all(abi))?;
    let ruleset_ref = &mut ruleset;

    if env::var_os(ENV_TCP_BIND_NAME).is_some() {
        ruleset_ref.handle_access(AccessNet::BindTcp)?;
    }
    if env::var_os(ENV_TCP_CONNECT_NAME).is_some() {
        ruleset_ref.handle_access(AccessNet::ConnectTcp)?;
    }

    if let Some(scoped) = env::var_os(ENV_SCOPED_NAME) {
        let mut abstract_scoping = false;
        let mut signal_scoping = false;
        let scopes = scoped.to_string_lossy();
        let is_empty = scopes.is_empty();
        for scope in scopes.split(':').skip_while(move |_| is_empty) {
            match scope {
                "a" => {
                    if abstract_scoping {
                        bail!("Duplicate scope 'a'");
                    }
                    ruleset_ref.scope(Scope::AbstractUnixSocket)?;
                    abstract_scoping = true;
                }
                "s" => {
                    if signal_scoping {
                        bail!("Duplicate scope 's'");
                    }
                    ruleset_ref.scope(Scope::Signal)?;
                    signal_scoping = true;
                }
                _ => bail!("Unknown scope \"{scope}\""),
            }
        }
    }

    let status = ruleset
        .create()?
        .add_rules(PathEnv::new(ENV_FS_RO_NAME, AccessFs::from_read(abi))?.iter())?
        .add_rules(PathEnv::new(ENV_FS_RW_NAME, AccessFs::from_all(abi))?.iter())?
        .add_rules(PortEnv::new(ENV_TCP_BIND_NAME, AccessNet::BindTcp)?.iter())?
        .add_rules(PortEnv::new(ENV_TCP_CONNECT_NAME, AccessNet::ConnectTcp)?.iter())?
        .restrict_self()
        .expect("Failed to enforce ruleset");

    match status.landlock {
        // This should never happen because of the previous check:
        LandlockStatus::NotEnabled => {
            eprintln!(
                "Hint: Landlock is currently disabled. \
                It can be enabled in the kernel configuration by prepending \"landlock,\"
                to the content of CONFIG_LSM, or at boot time by setting the same content to
                the \"lsm\" kernel parameter."
            );
        }
        LandlockStatus::NotImplemented => {
            eprintln!(
                "Hint: Landlock is not built into the current kernel. \
                To support it, build the kernel with CONFIG_SECURITY_LANDLOCK=y and \
                prepend \"landlock,\" to the content of CONFIG_LSM."
            );
        }
        LandlockStatus::Available {
            kernel_abi: Some(raw_abi),
            ..
        } => {
            eprintln!(
                "Hint: This sandboxer only supports Landlock ABI version up to {abi} \
                whereas the current kernel supports Landlock ABI version {raw_abi}. \
                To leverage all Landlock features, update this sandboxer."
            );
        }
        LandlockStatus::Available {
            kernel_abi: None,
            effective_abi,
        } => {
            if effective_abi < abi {
                eprintln!(
                    "Hint: This sandboxer supports Landlock ABI version up to {abi} \
                    but the current kernel only supports Landlock ABI version {effective_abi}. \
                    To leverage all Landlock features, update the kernel."
                );
            } else if effective_abi > abi {
                // This should not happen because the ABI used by the sandboxer
                // should be the latest supported by the Landlock crate, and
                // they should be updated at the same time.
                eprintln!(
                    "Warning: This sandboxer only supports Landlock ABI version up to {abi} \
                    but the current kernel supports Landlock ABI version {effective_abi}. \
                    To leverage all Landlock features, update this sandboxer."
                );
            }
        }
    }
    if status.ruleset == RulesetStatus::NotEnforced {
        bail!("The ruleset cannot be enforced at all");
    }

    eprintln!("Executing the sandboxed command...");
    Err(Command::new(cmd_name)
        .env_remove(ENV_FS_RO_NAME)
        .env_remove(ENV_FS_RW_NAME)
        .env_remove(ENV_TCP_BIND_NAME)
        .env_remove(ENV_TCP_CONNECT_NAME)
        .args(args)
        .exec()
        .into())
}


================================================
FILE: src/access.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::{
    private, AccessError, AddRuleError, AddRulesError, BitFlags, CompatError, CompatResult,
    HandleAccessError, HandleAccessesError, Ruleset, TailoredCompatLevel, TryCompat, ABI,
};
use enumflags2::BitFlag;

#[cfg(test)]
use crate::{make_bitflags, AccessFs, CompatLevel, CompatState, Compatibility};

pub trait Access: BitFlag + private::Sealed {
    /// Gets the access rights defined by a specific [`ABI`].
    fn from_all(abi: ABI) -> BitFlags<Self>;
}

// This HandledAccess trait is useful to document the API.
pub trait HandledAccess: Access {}

pub trait PrivateHandledAccess: HandledAccess {
    fn ruleset_handle_access(
        ruleset: &mut Ruleset,
        access: BitFlags<Self>,
    ) -> Result<(), HandleAccessesError>
    where
        Self: Access;

    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError
    where
        Self: Access;

    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError
    where
        Self: Access;
}

// Creates an illegal/overflowed BitFlags<T> with all its bits toggled, including undefined ones.
fn full_negation<T>(flags: BitFlags<T>) -> BitFlags<T>
where
    T: Access,
{
    unsafe { BitFlags::<T>::from_bits_unchecked(!flags.bits()) }
}

#[test]
fn bit_flags_full_negation() {
    let scoped_negation = !BitFlags::<AccessFs>::all();
    assert_eq!(scoped_negation, BitFlags::<AccessFs>::empty());
    // !BitFlags::<AccessFs>::all() could be equal to full_negation(BitFlags::<AccessFs>::all()))
    // if all the 64-bits would be used, which is not currently the case.
    assert_ne!(scoped_negation, full_negation(BitFlags::<AccessFs>::all()));
}

impl<A> TailoredCompatLevel for BitFlags<A> where A: Access {}

impl<A> TryCompat<A> for BitFlags<A>
where
    A: Access,
{
    fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>> {
        if self.is_empty() {
            // Empty access-rights would result to a runtime error.
            Err(AccessError::Empty.into())
        } else if !Self::all().contains(*self) {
            // Unknown access-rights (at build time) would result to a runtime error.
            // This can only be reached by using the unsafe BitFlags::from_bits_unchecked().
            Err(AccessError::Unknown {
                access: *self,
                unknown: *self & full_negation(Self::all()),
            }
            .into())
        } else {
            let compat = *self & A::from_all(abi);
            let ret = if compat.is_empty() {
                Ok(CompatResult::No(
                    AccessError::Incompatible { access: *self }.into(),
                ))
            } else if compat != *self {
                let error = AccessError::PartiallyCompatible {
                    access: *self,
                    incompatible: *self & full_negation(compat),
                }
                .into();
                Ok(CompatResult::Partial(error))
            } else {
                Ok(CompatResult::Full)
            };
            *self = compat;
            ret
        }
    }
}

#[test]
fn compat_bit_flags() {
    use crate::ABI;

    let mut compat: Compatibility = ABI::V1.into();
    assert!(compat.state == CompatState::Init);

    let ro_access = make_bitflags!(AccessFs::{Execute | ReadFile | ReadDir});
    assert_eq!(
        ro_access,
        ro_access
            .try_compat(compat.abi(), compat.level, &mut compat.state)
            .unwrap()
            .unwrap()
    );
    assert!(compat.state == CompatState::Full);

    let empty_access = BitFlags::<AccessFs>::empty();
    assert!(matches!(
        empty_access
            .try_compat(compat.abi(), compat.level, &mut compat.state)
            .unwrap_err(),
        CompatError::Access(AccessError::Empty)
    ));

    let all_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked(1 << 63) };
    assert!(matches!(
        all_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
        CompatError::Access(AccessError::Unknown { access, unknown }) if access == all_unknown_access && unknown == all_unknown_access
    ));
    // An error makes the state final.
    assert!(compat.state == CompatState::Dummy);

    let some_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked(1 << 63 | 1) };
    assert!(matches!(
        some_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
        CompatError::Access(AccessError::Unknown { access, unknown }) if access == some_unknown_access && unknown == all_unknown_access
    ));
    assert!(compat.state == CompatState::Dummy);

    compat = ABI::Unsupported.into();

    // Tests that the ruleset is marked as unsupported.
    assert!(compat.state == CompatState::Init);

    // Access-rights are valid (but ignored) when they are not required for the current ABI.
    assert_eq!(
        None,
        ro_access
            .try_compat(compat.abi(), compat.level, &mut compat.state)
            .unwrap()
    );

    assert!(compat.state == CompatState::No);

    // Access-rights are not valid when they are required for the current ABI.
    compat.level = Some(CompatLevel::HardRequirement);
    assert!(matches!(
        ro_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
        CompatError::Access(AccessError::Incompatible { access }) if access == ro_access
    ));

    compat = ABI::V1.into();

    // Tests that the ruleset is marked as the unknown compatibility state.
    assert!(compat.state == CompatState::Init);

    // Access-rights are valid (but ignored) when they are not required for the current ABI.
    assert_eq!(
        ro_access,
        ro_access
            .try_compat(compat.abi(), compat.level, &mut compat.state)
            .unwrap()
            .unwrap()
    );

    // Tests that the ruleset is in an unsupported state, which is important to be able to still
    // enforce no_new_privs.
    assert!(compat.state == CompatState::Full);

    let v2_access = ro_access | AccessFs::Refer;

    // Access-rights are not valid when they are required for the current ABI.
    compat.level = Some(CompatLevel::HardRequirement);
    assert!(matches!(
        v2_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
        CompatError::Access(AccessError::PartiallyCompatible { access, incompatible })
            if access == v2_access && incompatible == AccessFs::Refer
    ));
}


================================================
FILE: src/compat.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::{uapi, Access, CompatError};
use std::fmt::{self, Display, Formatter};
use std::io::Error;

#[cfg(test)]
use std::convert::TryInto;
#[cfg(test)]
use strum::{EnumCount, IntoEnumIterator};
#[cfg(test)]
use strum_macros::{EnumCount as EnumCountMacro, EnumIter};

/// Version of the Landlock [ABI](https://en.wikipedia.org/wiki/Application_binary_interface).
///
/// `ABI` enables getting the features supported by a specific Landlock ABI
/// (without relying on the kernel version which may not be accessible or patched).
/// For example, [`AccessFs::from_all(ABI::V1)`](Access::from_all)
/// gets all the file system access rights defined by the first version.
///
/// Without `ABI`, it would be hazardous to rely on the the full set of access flags
/// (e.g., `BitFlags::<AccessFs>::all()` or `BitFlags::ALL`),
/// a moving target that would change the semantics of your Landlock rule
/// when migrating to a newer version of this crate.
/// Indeed, a simple `cargo update` or `cargo install` run by any developer
/// can result in a new version of this crate (fixing bugs or bringing non-breaking changes).
/// This crate cannot give any guarantee concerning the new restrictions resulting from
/// these unknown bits (i.e. access rights) that would not be controlled by your application but by
/// a future version of this crate instead.
/// Because we cannot know what the effect on your application of an unknown restriction would be
/// when handling an untested Landlock access right (i.e. denied-by-default access),
/// it could trigger bugs in your application.
///
/// This crate provides a set of tools to sandbox as much as possible
/// while guaranteeing a consistent behavior thanks to the [`Compatible`] methods.
/// You should also test with different relevant kernel versions,
/// see [landlock-test-tools](https://github.com/landlock-lsm/landlock-test-tools) and
/// [CI integration](https://github.com/landlock-lsm/rust-landlock/pull/41).
///
/// This way, we can have the guarantee that the use of a set of tested Landlock ABI works as
/// expected because features brought by newer Landlock ABI will never be enabled by default
/// (cf. [Linux kernel compatibility contract](https://docs.kernel.org/userspace-api/landlock.html#compatibility)).
///
/// In a nutshell, test the access rights you request on a kernel that support them and
/// on a kernel that doesn't support them.
///
/// Derived `Debug` formats are [not stable](https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability).
#[cfg_attr(test, derive(EnumIter, EnumCountMacro))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum ABI {
    /// Kernel not supporting Landlock, either because it is not built with Landlock
    /// or Landlock is not enabled at boot.
    Unsupported = 0,
    /// First Landlock ABI, introduced with
    /// [Linux 5.13](https://git.kernel.org/stable/c/17ae69aba89dbfa2139b7f8024b757ab3cc42f59).
    V1 = 1,
    /// Second Landlock ABI, introduced with
    /// [Linux 5.19](https://git.kernel.org/stable/c/cb44e4f061e16be65b8a16505e121490c66d30d0).
    V2 = 2,
    /// Third Landlock ABI, introduced with
    /// [Linux 6.2](https://git.kernel.org/stable/c/299e2b1967578b1442128ba8b3e86ed3427d3651).
    V3 = 3,
    /// Fourth Landlock ABI, introduced with
    /// [Linux 6.7](https://git.kernel.org/stable/c/136cc1e1f5be75f57f1e0404b94ee1c8792cb07d).
    V4 = 4,
    /// Fifth Landlock ABI, introduced with
    /// [Linux 6.10](https://git.kernel.org/stable/c/2fc0e7892c10734c1b7c613ef04836d57d4676d5).
    V5 = 5,
    /// Sixth Landlock ABI, introduced with
    /// [Linux 6.12](https://git.kernel.org/stable/c/e1b061b444fb01c237838f0d8238653afe6a8094).
    V6 = 6,
}

// ABI should not be dynamically created (in other crates) according to the running kernel
// to avoid inconsistent behaviors and non-determinism. Creating ABIs based on runtime detection
// can lead to unreliable sandboxing where rules might differ between executions.
impl ABI {
    #[cfg(test)]
    fn is_known(value: i32) -> bool {
        value > 0 && value < ABI::COUNT as i32
    }
}

/// Converting from an integer to an ABI should only be used for testing.
/// Indeed, manually setting the ABI can lead to inconsistent and unexpected behaviors.
/// Instead, just use the appropriate access rights, this library will handle the rest.
impl From<i32> for ABI {
    fn from(value: i32) -> ABI {
        match value {
            n if n <= 0 => ABI::Unsupported,
            1 => ABI::V1,
            2 => ABI::V2,
            3 => ABI::V3,
            4 => ABI::V4,
            5 => ABI::V5,
            // Returns the greatest known ABI.
            _ => ABI::V6,
        }
    }
}

#[test]
fn abi_from() {
    // EOPNOTSUPP (-95), ENOSYS (-38)
    for n in [-95, -38, -1, 0] {
        assert_eq!(ABI::from(n), ABI::Unsupported);
    }

    let mut last_i = 1;
    let mut last_abi = ABI::Unsupported;
    for (i, abi) in ABI::iter().enumerate() {
        last_i = i.try_into().unwrap();
        last_abi = abi;
        assert_eq!(ABI::from(last_i), last_abi);
    }

    assert_eq!(ABI::from(last_i + 1), last_abi);
    assert_eq!(ABI::from(999), last_abi);
}

#[test]
fn known_abi() {
    assert!(!ABI::is_known(-1));
    assert!(!ABI::is_known(0));
    assert!(!ABI::is_known(999));

    let mut last_i = -1;
    for (i, _) in ABI::iter().enumerate().skip(1) {
        last_i = i as i32;
        assert!(ABI::is_known(last_i));
    }
    assert!(!ABI::is_known(last_i + 1));
}

impl Display for ABI {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            ABI::Unsupported => write!(f, "unsupported"),
            v => (*v as u32).fmt(f),
        }
    }
}

/// Status of Landlock support for the running system.
///
/// This enum is used to represent the status of the Landlock support for the system where the code
/// is executed. It can indicate whether Landlock is available or not.
///
/// # Warning
///
/// Sandboxed programs should only use this data to log or provide information to users,
/// not to change their behavior according to this status.  Indeed, the `Ruleset` and the other
/// types are designed to handle the compatibility in a simple and safe way.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LandlockStatus {
    /// Landlock is supported but not enabled (`EOPNOTSUPP`).
    NotEnabled,
    /// Landlock is not implemented (i.e. not built into the running kernel: `ENOSYS`).
    NotImplemented,
    /// Landlock is available and working on the running system.
    ///
    /// This indicates that the kernel supports Landlock and it's properly enabled.
    /// The crate uses the `effective_abi` for all operations, which represents
    /// the highest ABI version that both the kernel and this crate understand.
    Available {
        /// The effective ABI version that this crate will use for Landlock operations.
        /// This is the intersection of what the kernel supports and what this crate knows about.
        effective_abi: ABI,
        /// The actual kernel ABI version when it's newer than any ABI supported by this crate.
        ///
        /// If `Some(version)`, it means the running kernel supports Landlock ABI `version`
        /// which is higher than the latest ABI known by this crate.
        ///
        /// This field is purely informational and is never used for Landlock operations.
        /// The crate always and only uses `effective_abi` for all functionality.
        kernel_abi: Option<i32>,
    },
}

impl LandlockStatus {
    // Must remain private to avoid inconsistent behavior using such unknown-at-build-time ABI
    // e.g., AccessFs::from_all(ABI::new_current())
    //
    // This should not be Default::default() because the returned value would may not be the same
    // for all users.
    fn current() -> Self {
        // Landlock ABI version starts at 1 but errno is only set for negative values.
        let v = unsafe {
            uapi::landlock_create_ruleset(
                std::ptr::null(),
                0,
                uapi::LANDLOCK_CREATE_RULESET_VERSION,
            )
        };
        if v < 0 {
            // The only possible error values should be EOPNOTSUPP and ENOSYS.
            match Error::last_os_error().raw_os_error() {
                Some(libc::EOPNOTSUPP) => Self::NotEnabled,
                _ => Self::NotImplemented,
            }
        } else {
            let abi = ABI::from(v);
            Self::Available {
                effective_abi: abi,
                kernel_abi: (v != abi as i32).then_some(v),
            }
        }
    }
}

// Test against the running kernel.
#[test]
fn test_current_landlock_status() {
    let status = LandlockStatus::current();
    if *TEST_ABI == ABI::Unsupported {
        assert_eq!(status, LandlockStatus::NotImplemented);
    } else {
        assert!(
            matches!(status, LandlockStatus::Available { effective_abi, .. } if effective_abi == *TEST_ABI)
        );
        if std::env::var(TEST_ABI_ENV_NAME).is_ok() {
            // We cannot reliably check for unknown kernel.
            assert!(matches!(
                status,
                LandlockStatus::Available {
                    kernel_abi: None,
                    ..
                }
            ));
        }
    }
}

impl From<LandlockStatus> for ABI {
    fn from(status: LandlockStatus) -> Self {
        match status {
            // The only possible error values should be EOPNOTSUPP and ENOSYS,
            // but let's convert all kind of errors as unsupported.
            LandlockStatus::NotEnabled | LandlockStatus::NotImplemented => ABI::Unsupported,
            LandlockStatus::Available { effective_abi, .. } => effective_abi,
        }
    }
}

// This is only useful to tests and should not be exposed publicly because
// the mapping can only be partial.
#[cfg(test)]
impl From<ABI> for LandlockStatus {
    fn from(abi: ABI) -> Self {
        match abi {
            // Convert to ENOSYS because of check_ruleset_support() and ruleset_unsupported() tests.
            ABI::Unsupported => Self::NotImplemented,
            _ => Self::Available {
                effective_abi: abi,
                kernel_abi: None,
            },
        }
    }
}

#[cfg(test)]
pub(crate) static TEST_ABI_ENV_NAME: &str = "LANDLOCK_CRATE_TEST_ABI";

#[cfg(test)]
lazy_static! {
    pub(crate) static ref TEST_ABI: ABI = match std::env::var("LANDLOCK_CRATE_TEST_ABI") {
        Ok(s) => {
            let n = s.parse::<i32>().unwrap();
            if ABI::is_known(n) || n == 0 {
                ABI::from(n)
            } else {
                panic!("Unknown ABI: {n}");
            }
        }
        Err(std::env::VarError::NotPresent) => LandlockStatus::current().into(),
        Err(e) => panic!("Failed to read LANDLOCK_CRATE_TEST_ABI: {e}"),
    };
}

#[cfg(test)]
pub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support: Option<ABI>) -> bool {
    mock < partial_support
        || mock <= *TEST_ABI
        || if let Some(full) = full_support {
            full <= *TEST_ABI
        } else {
            partial_support <= *TEST_ABI
        }
}

#[cfg(test)]
pub(crate) fn get_errno_from_landlock_status() -> Option<i32> {
    match LandlockStatus::current() {
        LandlockStatus::NotImplemented | LandlockStatus::NotEnabled => {
            match Error::last_os_error().raw_os_error() {
                // Returns ENOSYS when the kernel is not built with Landlock support,
                // or EOPNOTSUPP when Landlock is supported but disabled at boot time.
                ret @ Some(libc::ENOSYS | libc::EOPNOTSUPP) => ret,
                // Other values can only come from bogus seccomp filters or debugging tampering.
                ret => {
                    eprintln!("Current kernel should support this Landlock ABI according to $LANDLOCK_CRATE_TEST_ABI");
                    eprintln!("Unexpected result: {ret:?}");
                    unreachable!();
                }
            }
        }
        LandlockStatus::Available { .. } => None,
    }
}

#[test]
fn current_kernel_abi() {
    // Ensures that the tested Landlock ABI is the latest known version supported by the running
    // kernel.  If this test failed, you need set the LANDLOCK_CRATE_TEST_ABI environment variable
    // to the Landlock ABI version supported by your kernel.  With a missing variable, the latest
    // Landlock ABI version known by this crate is automatically set.
    // From Linux 5.13 to 5.18, you need to run: LANDLOCK_CRATE_TEST_ABI=1 cargo test
    let test_abi = *TEST_ABI;
    let current_abi = LandlockStatus::current().into();
    println!(
        "Current kernel version: {}",
        std::fs::read_to_string("/proc/version")
            .unwrap_or_else(|_| "unknown".into())
            .trim()
    );
    println!("Expected Landlock ABI {test_abi:?} whereas the current ABI is {current_abi:#?}");
    assert_eq!(test_abi, current_abi);
}

// CompatState is not public outside this crate.
/// Returned by ruleset builder.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CompatState {
    /// Initial undefined state.
    Init,
    /// All requested restrictions are enforced.
    Full,
    /// Some requested restrictions are enforced, following a best-effort approach.
    Partial,
    /// The running system doesn't support Landlock.
    No,
    /// Final unsupported state.
    Dummy,
}

impl CompatState {
    fn update(&mut self, other: Self) {
        *self = match (*self, other) {
            (CompatState::Init, other) => other,
            (CompatState::Dummy, _) => CompatState::Dummy,
            (_, CompatState::Dummy) => CompatState::Dummy,
            (CompatState::No, CompatState::No) => CompatState::No,
            (CompatState::Full, CompatState::Full) => CompatState::Full,
            (_, _) => CompatState::Partial,
        }
    }
}

#[test]
fn compat_state_update_1() {
    let mut state = CompatState::Full;

    state.update(CompatState::Full);
    assert_eq!(state, CompatState::Full);

    state.update(CompatState::No);
    assert_eq!(state, CompatState::Partial);

    state.update(CompatState::Full);
    assert_eq!(state, CompatState::Partial);

    state.update(CompatState::Full);
    assert_eq!(state, CompatState::Partial);

    state.update(CompatState::No);
    assert_eq!(state, CompatState::Partial);

    state.update(CompatState::Dummy);
    assert_eq!(state, CompatState::Dummy);

    state.update(CompatState::Full);
    assert_eq!(state, CompatState::Dummy);
}

#[test]
fn compat_state_update_2() {
    let mut state = CompatState::Full;

    state.update(CompatState::Full);
    assert_eq!(state, CompatState::Full);

    state.update(CompatState::No);
    assert_eq!(state, CompatState::Partial);

    state.update(CompatState::Full);
    assert_eq!(state, CompatState::Partial);
}

#[cfg_attr(test, derive(PartialEq))]
#[derive(Copy, Clone, Debug)]
pub(crate) struct Compatibility {
    status: LandlockStatus,
    pub(crate) level: Option<CompatLevel>,
    pub(crate) state: CompatState,
}

impl From<LandlockStatus> for Compatibility {
    fn from(status: LandlockStatus) -> Self {
        Compatibility {
            status,
            level: Default::default(),
            state: CompatState::Init,
        }
    }
}

#[cfg(test)]
impl From<ABI> for Compatibility {
    fn from(abi: ABI) -> Self {
        Self::from(LandlockStatus::from(abi))
    }
}

impl Compatibility {
    // Compatibility is a semi-opaque struct.
    #[allow(clippy::new_without_default)]
    pub(crate) fn new() -> Self {
        LandlockStatus::current().into()
    }

    pub(crate) fn update(&mut self, state: CompatState) {
        self.state.update(state);
    }

    pub(crate) fn abi(&self) -> ABI {
        self.status.into()
    }

    pub(crate) fn status(&self) -> LandlockStatus {
        self.status
    }
}

pub(crate) mod private {
    use crate::CompatLevel;

    pub trait OptionCompatLevelMut {
        fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel>;
    }
}

/// Properly handles runtime unsupported features.
///
/// This guarantees consistent behaviors across crate users
/// and runtime kernels even if this crate get new features.
/// It eases backward compatibility and enables future-proofness.
///
/// Landlock is a security feature designed to help improve security of a running system
/// thanks to application developers.
/// To protect users as much as possible,
/// compatibility with the running system should then be handled in a best-effort way,
/// contrary to common system features.
/// In some circumstances
/// (e.g. applications carefully designed to only be run with a specific set of kernel features),
/// it may be required to error out if some of these features are not available
/// and will then not be enforced.
pub trait Compatible: Sized + private::OptionCompatLevelMut {
    /// To enable a best-effort security approach,
    /// Landlock features that are not supported by the running system
    /// are silently ignored by default,
    /// which is a sane choice for most use cases.
    /// However, on some rare circumstances,
    /// developers may want to have some guarantees that their applications
    /// will not run if a certain level of sandboxing is not possible.
    /// If we really want to error out when not all our requested requirements are met,
    /// then we can configure it with `set_compatibility()`.
    ///
    /// The `Compatible` trait is implemented for all object builders
    /// (e.g. [`Ruleset`](crate::Ruleset)).
    /// Such builders have a set of methods to incrementally build an object.
    /// These build methods rely on kernel features that may not be available at runtime.
    /// The `set_compatibility()` method enables to control the effect of
    /// the following build method calls starting after the `set_compatibility()` call.
    /// Such effect can be:
    /// * to silently ignore unsupported features
    ///   and continue building ([`CompatLevel::BestEffort`]);
    /// * to silently ignore unsupported features
    ///   and ignore the whole build ([`CompatLevel::SoftRequirement`]);
    /// * to return an error for any unsupported feature ([`CompatLevel::HardRequirement`]).
    ///
    /// Taking [`Ruleset`](crate::Ruleset) as an example,
    /// the [`handle_access()`](crate::RulesetAttr::handle_access()) build method
    /// returns a [`Result`] that can be [`Err(RulesetError)`](crate::RulesetError)
    /// with a nested [`CompatError`].
    /// Such error can only occur with a running Linux kernel not supporting the requested
    /// Landlock accesses *and* if the current compatibility level is
    /// [`CompatLevel::HardRequirement`].
    /// However, such error is not possible with [`CompatLevel::BestEffort`]
    /// nor [`CompatLevel::SoftRequirement`].
    ///
    /// The order of this call is important because
    /// it defines the behavior of the following build method calls that return a [`Result`].
    /// If `set_compatibility(CompatLevel::HardRequirement)` is called on an object,
    /// then a [`CompatError`] may be returned for the next method calls,
    /// until the next call to `set_compatibility()`.
    /// This enables to change the behavior of a set of build method calls,
    /// for instance to be sure that the sandbox will at least restrict some access rights.
    ///
    /// New objects inherit the compatibility configuration of their parents, if any.
    /// For instance, [`Ruleset::create()`](crate::Ruleset::create()) returns
    /// a [`RulesetCreated`](crate::RulesetCreated) object that inherits the
    /// `Ruleset`'s compatibility configuration.
    ///
    /// # Example with `SoftRequirement`
    ///
    /// Let's say an application legitimately needs to rename files between directories.
    /// Because of [previous Landlock limitations](https://docs.kernel.org/userspace-api/landlock.html#file-renaming-and-linking-abi-2),
    /// this was forbidden with the [first version of Landlock](ABI::V1),
    /// but it is now handled starting with the [second version](ABI::V2).
    /// For this use case, we only want the application to be sandboxed
    /// if we have the guarantee that it will not break a legitimate usage (i.e. rename files).
    /// We then create a ruleset which will either support file renaming
    /// (thanks to [`AccessFs::Refer`](crate::AccessFs::Refer)) or silently do nothing.
    ///
    /// ```
    /// use landlock::*;
    ///
    /// fn ruleset_handling_renames() -> Result<RulesetCreated, RulesetError> {
    ///     Ok(Ruleset::default()
    ///         // This ruleset must either handle the AccessFs::Refer right,
    ///         // or it must silently ignore the whole sandboxing.
    ///         .set_compatibility(CompatLevel::SoftRequirement)
    ///         .handle_access(AccessFs::Refer)?
    ///         // However, this ruleset may also handle other (future) access rights
    ///         // if they are supported by the running kernel.
    ///         .set_compatibility(CompatLevel::BestEffort)
    ///         .handle_access(AccessFs::from_all(ABI::V6))?
    ///         .create()?)
    /// }
    /// ```
    ///
    /// # Example with `HardRequirement`
    ///
    /// Security-dedicated applications may want to ensure that
    /// an untrusted software component is subject to a minimum of restrictions before launching it.
    /// In this case, we want to create a ruleset which will at least support
    /// all restrictions provided by the [first version of Landlock](ABI::V1),
    /// and opportunistically handle restrictions supported by newer kernels.
    ///
    /// ```
    /// use landlock::*;
    ///
    /// fn ruleset_fragile() -> Result<RulesetCreated, RulesetError> {
    ///     Ok(Ruleset::default()
    ///         // This ruleset must either handle at least all accesses defined by
    ///         // the first Landlock version (e.g. AccessFs::WriteFile),
    ///         // or the following handle_access() call must return a wrapped
    ///         // AccessError<AccessFs>::Incompatible error.
    ///         .set_compatibility(CompatLevel::HardRequirement)
    ///         .handle_access(AccessFs::from_all(ABI::V1))?
    ///         // However, this ruleset may also handle new access rights
    ///         // (e.g. AccessFs::Refer defined by the second version of Landlock)
    ///         // if they are supported by the running kernel,
    ///         // but without returning any error otherwise.
    ///         .set_compatibility(CompatLevel::BestEffort)
    ///         .handle_access(AccessFs::from_all(ABI::V6))?
    ///         .create()?)
    /// }
    /// ```
    fn set_compatibility(mut self, level: CompatLevel) -> Self {
        *self.as_option_compat_level_mut() = Some(level);
        self
    }

    /// Cf. [`set_compatibility()`](Compatible::set_compatibility()):
    ///
    /// - `set_best_effort(true)` translates to `set_compatibility(CompatLevel::BestEffort)`.
    ///
    /// - `set_best_effort(false)` translates to `set_compatibility(CompatLevel::HardRequirement)`.
    #[deprecated(note = "Use set_compatibility() instead")]
    fn set_best_effort(self, best_effort: bool) -> Self
    where
        Self: Sized,
    {
        self.set_compatibility(match best_effort {
            true => CompatLevel::BestEffort,
            false => CompatLevel::HardRequirement,
        })
    }
}

#[test]
#[allow(deprecated)]
fn deprecated_set_best_effort() {
    use crate::{CompatLevel, Compatible, Ruleset};

    assert_eq!(
        Ruleset::default().set_best_effort(true).compat,
        Ruleset::default()
            .set_compatibility(CompatLevel::BestEffort)
            .compat
    );
    assert_eq!(
        Ruleset::default().set_best_effort(false).compat,
        Ruleset::default()
            .set_compatibility(CompatLevel::HardRequirement)
            .compat
    );
}

/// See the [`Compatible`] documentation.
#[cfg_attr(test, derive(EnumIter))]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompatLevel {
    /// Takes into account the build requests if they are supported by the running system,
    /// or silently ignores them otherwise.
    /// Never returns a compatibility error.
    #[default]
    BestEffort,
    /// Takes into account the build requests if they are supported by the running system,
    /// or silently ignores the whole build object otherwise.
    /// Never returns a compatibility error.
    /// If not supported,
    /// the call to [`RulesetCreated::restrict_self()`](crate::RulesetCreated::restrict_self())
    /// will return a
    /// [`RestrictionStatus { ruleset: RulesetStatus::NotEnforced, no_new_privs: false, }`](crate::RestrictionStatus).
    SoftRequirement,
    /// Takes into account the build requests if they are supported by the running system,
    /// or returns a compatibility error otherwise ([`CompatError`]).
    HardRequirement,
}

impl From<Option<CompatLevel>> for CompatLevel {
    fn from(opt: Option<CompatLevel>) -> Self {
        match opt {
            None => CompatLevel::default(),
            Some(ref level) => *level,
        }
    }
}

// TailoredCompatLevel could be replaced with AsMut<Option<CompatLevel>>, but only traits defined
// in the current crate can be implemented for types defined outside of the crate.  Furthermore it
// provides a default implementation which is handy for types such as BitFlags.
pub trait TailoredCompatLevel {
    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
    where
        L: Into<CompatLevel>,
    {
        parent_level.into()
    }
}

impl<T> TailoredCompatLevel for T
where
    Self: Compatible,
{
    // Every Compatible trait implementation returns its own compatibility level, if set.
    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
    where
        L: Into<CompatLevel>,
    {
        // Using a mutable reference is not required but it makes the code simpler (no double AsRef
        // implementations for each Compatible types), and more importantly it guarantees
        // consistency with Compatible::set_compatibility().
        match self.as_option_compat_level_mut() {
            None => parent_level.into(),
            // Returns the most constrained compatibility level.
            Some(ref level) => parent_level.into().max(*level),
        }
    }
}

#[test]
fn tailored_compat_level() {
    use crate::{AccessFs, PathBeneath, PathFd};

    fn new_path(level: CompatLevel) -> PathBeneath<PathFd> {
        PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Execute).set_compatibility(level)
    }

    for parent_level in CompatLevel::iter() {
        assert_eq!(
            new_path(CompatLevel::BestEffort).tailored_compat_level(parent_level),
            parent_level
        );
        assert_eq!(
            new_path(CompatLevel::HardRequirement).tailored_compat_level(parent_level),
            CompatLevel::HardRequirement
        );
    }

    assert_eq!(
        new_path(CompatLevel::SoftRequirement).tailored_compat_level(CompatLevel::SoftRequirement),
        CompatLevel::SoftRequirement
    );

    for child_level in CompatLevel::iter() {
        assert_eq!(
            new_path(child_level).tailored_compat_level(CompatLevel::BestEffort),
            child_level
        );
        assert_eq!(
            new_path(child_level).tailored_compat_level(CompatLevel::HardRequirement),
            CompatLevel::HardRequirement
        );
    }
}

// CompatResult is not public outside this crate.
pub enum CompatResult<A>
where
    A: Access,
{
    // Fully matches the request.
    Full,
    // Partially matches the request.
    Partial(CompatError<A>),
    // Doesn't matches the request.
    No(CompatError<A>),
}

// TryCompat is not public outside this crate.
pub trait TryCompat<A>
where
    Self: Sized + TailoredCompatLevel,
    A: Access,
{
    fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>>;

    // Default implementation for objects without children.
    //
    // If returning something other than Ok(Some(self)), the implementation must use its own
    // compatibility level, if any, with self.tailored_compat_level(default_compat_level), and pass
    // it with the abi and compat_state to each child.try_compat().  See PathBeneath implementation
    // and the self.allowed_access.try_compat() call.
    //
    // # Warning
    //
    // Errors must be prioritized over incompatibility (i.e. return Err(e) over Ok(None)) for all
    // children.
    fn try_compat_children<L>(
        self,
        _abi: ABI,
        _parent_level: L,
        _compat_state: &mut CompatState,
    ) -> Result<Option<Self>, CompatError<A>>
    where
        L: Into<CompatLevel>,
    {
        Ok(Some(self))
    }

    // Update compat_state and return an error according to try_compat_*() error, or to the
    // compatibility level, i.e. either route compatible object or error.
    fn try_compat<L>(
        mut self,
        abi: ABI,
        parent_level: L,
        compat_state: &mut CompatState,
    ) -> Result<Option<Self>, CompatError<A>>
    where
        L: Into<CompatLevel>,
    {
        let compat_level = self.tailored_compat_level(parent_level);
        let some_inner = match self.try_compat_inner(abi) {
            Ok(CompatResult::Full) => {
                compat_state.update(CompatState::Full);
                true
            }
            Ok(CompatResult::Partial(error)) => match compat_level {
                CompatLevel::BestEffort => {
                    compat_state.update(CompatState::Partial);
                    true
                }
                CompatLevel::SoftRequirement => {
                    compat_state.update(CompatState::Dummy);
                    false
                }
                CompatLevel::HardRequirement => {
                    compat_state.update(CompatState::Dummy);
                    return Err(error);
                }
            },
            Ok(CompatResult::No(error)) => match compat_level {
                CompatLevel::BestEffort => {
                    compat_state.update(CompatState::No);
                    false
                }
                CompatLevel::SoftRequirement => {
                    compat_state.update(CompatState::Dummy);
                    false
                }
                CompatLevel::HardRequirement => {
                    compat_state.update(CompatState::Dummy);
                    return Err(error);
                }
            },
            Err(error) => {
                // Safeguard to help for test consistency.
                compat_state.update(CompatState::Dummy);
                return Err(error);
            }
        };

        // At this point, any inner error have been returned, so we can proceed with
        // try_compat_children()?.
        match self.try_compat_children(abi, compat_level, compat_state)? {
            Some(n) if some_inner => Ok(Some(n)),
            _ => Ok(None),
        }
    }
}


================================================
FILE: src/errata.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::compat::ABI;
use crate::{uapi, BitFlags};
use enumflags2::bitflags;

/// Fixed kernel issues for the running Landlock implementation.
///
/// Each variant represents a specific bug fix that may have been
/// backported to the running kernel.  Use [`Erratum::current()`]
/// before building a [`Ruleset`](crate::Ruleset) to decide which
/// features are safe to use.
///
/// An [`ABI`] version can be converted into the set of applicable errata
/// with `BitFlags::<Erratum>::from(abi)`.
///
/// # Warning
///
/// Most applications should **not** check errata.  Disabling a sandboxing
/// feature because an erratum is not fixed could leave the system **less**
/// secure than using Landlock's best-effort protection with the buggy
/// feature enabled.  Errata should only be used to **add** features
/// (e.g., enabling a restriction only when its bug is confirmed fixed),
/// never to remove them.
#[bitflags]
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Erratum {
    /// Erratum 1 (ABI 4): non-TCP stream sockets (SMC, MPTCP, SCTP)
    /// were incorrectly restricted by TCP access rights during
    /// `bind(2)` and `connect(2)`.
    ///
    /// Affects [`crate::AccessNet::BindTcp`] and [`crate::AccessNet::ConnectTcp`].
    ///
    /// See [erratum 1](https://docs.kernel.org/userspace-api/landlock.html#erratum-1-tcp-socket-identification).
    TcpSocketIdentification = 1 << 0,
    /// Erratum 2 (ABI 6): signal scoping was overly restrictive,
    /// preventing sandboxed threads from signaling other threads
    /// within the same process in different domains.
    ///
    /// Affects [`crate::Scope::Signal`].
    ///
    /// See [erratum 2](https://docs.kernel.org/userspace-api/landlock.html#erratum-2-scoped-signal-handling).
    ScopedSignalHandling = 1 << 1,
    /// Erratum 3 (ABI 1): access rights could be widened through
    /// rename or link actions on disconnected directories under
    /// bind mounts, potentially bypassing `LANDLOCK_ACCESS_FS_REFER`
    /// restrictions.
    ///
    /// See [erratum 3](https://docs.kernel.org/userspace-api/landlock.html#erratum-3-disconnected-directory-handling).
    DisconnectedDirectoryHandling = 1 << 2,
}

impl Erratum {
    /// Queries the running kernel for fixed errata.
    ///
    /// Returns a bitmask of errata that have been fixed in the running
    /// kernel.  Unknown errata bits from newer kernels are preserved.
    /// Returns empty if the kernel doesn't support the errata interface.
    pub fn current() -> BitFlags<Self> {
        let ret = unsafe {
            uapi::landlock_create_ruleset(std::ptr::null(), 0, uapi::LANDLOCK_CREATE_RULESET_ERRATA)
        };
        if ret >= 0 {
            // SAFETY: The kernel may return bits unknown to this crate version.
            // Using from_bits_unchecked to preserve them.
            unsafe { BitFlags::from_bits_unchecked(ret as u32) }
        } else {
            BitFlags::empty()
        }
    }
}

/// Converts an [`ABI`] version into the set of errata applicable to that ABI.
///
/// An erratum is applicable if the ABI includes the feature affected by the bug.
/// For example, [`Erratum::TcpSocketIdentification`] is only applicable to
/// [`ABI::V4`] and later, since TCP access rights were introduced in that version.
///
/// Uses the same incremental accumulation pattern as
/// [`AccessFs::from_write()`](crate::AccessFs::from_write).
///
/// # Stability
///
/// The set of errata returned for a given ABI may grow in future versions
/// of this crate as new kernel bug fixes are identified and backported.
/// Do not rely on the exact set being stable across crate versions.
impl From<ABI> for BitFlags<Erratum> {
    fn from(abi: ABI) -> Self {
        match abi {
            ABI::Unsupported => BitFlags::empty(),
            // Erratum 3: disconnected directory handling (FS, ABI 1+).
            ABI::V1 | ABI::V2 | ABI::V3 => Erratum::DisconnectedDirectoryHandling.into(),
            // Erratum 1: TCP socket identification (net, ABI 4+).
            ABI::V4 | ABI::V5 => Self::from(ABI::V3) | Erratum::TcpSocketIdentification,
            // Erratum 2: scoped signal handling (scopes, ABI 6+).
            // When adding a new ABI version without new errata, append it here.
            ABI::V6 => Self::from(ABI::V5) | Erratum::ScopedSignalHandling,
        }
    }
}

/// Returns the set of errata that have not been backported yet for a given ABI.
///
/// This is the single source of truth for known backport gaps.  When an
/// erratum is backported to a kernel version, remove it from the
/// corresponding match arm.  The CI will catch mismatches.
#[cfg(test)]
fn not_backported_yet(abi: ABI) -> BitFlags<Erratum> {
    match abi {
        ABI::Unsupported => BitFlags::empty(),
        // TODO: erratum 3 (DisconnectedDirectoryHandling) should be backported.
        ABI::V1 | ABI::V2 => Erratum::DisconnectedDirectoryHandling.into(),
        // 6.4, 6.7, 6.10: EOL, no errata interface on stable.kernel.
        ABI::V3 | ABI::V4 | ABI::V5 => BitFlags::empty(),
        // 6.12: all errata backported.
        ABI::V6 => BitFlags::empty(),
    }
}

#[test]
fn errata_query() {
    // Verifies the syscall wrapper works on any kernel.
    let _errata = Erratum::current();
}

#[test]
fn errata_up_to_date() {
    use crate::compat::{ABI, TEST_ABI, TEST_ABI_ENV_NAME};

    // This test requires LANDLOCK_CRATE_TEST_ABI to be explicitly set because
    // the errata assertions are tied to specific CI kernel versions.  Without
    // it, TEST_ABI is auto-detected from the running kernel, but From<i32>
    // maps unknown ABI versions to the highest known one, making the
    // ABI-to-kernel mapping ambiguous (e.g., a 6.15 kernel maps to V6 before
    // ABI::V7 exists).  Since Erratum::current() queries the real kernel, the
    // expected errata for the declared ABI may not match.
    if std::env::var(TEST_ABI_ENV_NAME).is_err() {
        eprintln!("Skipping errata_up_to_date: {} not set", TEST_ABI_ENV_NAME,);
        return;
    }

    let current = Erratum::current();
    let applicable: BitFlags<Erratum> = (*TEST_ABI).into();
    let expected = applicable & !not_backported_yet(*TEST_ABI);

    // Kernel must never report errata for features absent from this ABI.
    assert!(
        current & !applicable == BitFlags::empty(),
        "kernel reported errata not applicable to ABI {:?}: {:?}",
        *TEST_ABI,
        current & !applicable,
    );

    match *TEST_ABI {
        ABI::Unsupported => assert!(current.is_empty()),
        ABI::V1 | ABI::V2 => assert_eq!(current, expected),
        // 6.4, 6.7, 6.10: EOL, no errata interface on stable.kernel.
        ABI::V3 | ABI::V4 | ABI::V5 => {}
        ABI::V6 => assert_eq!(current, expected),
    }
}


================================================
FILE: src/errors.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::{Access, AccessFs, AccessNet, BitFlags, HandledAccess, PrivateHandledAccess, Scope};
use libc::c_int;
use std::io;
use std::path::PathBuf;
use thiserror::Error;

/// Maps to all errors that can be returned by a ruleset action.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RulesetError {
    #[error(transparent)]
    HandleAccesses(#[from] HandleAccessesError),
    #[error(transparent)]
    CreateRuleset(#[from] CreateRulesetError),
    #[error(transparent)]
    AddRules(#[from] AddRulesError),
    #[error(transparent)]
    RestrictSelf(#[from] RestrictSelfError),
    #[error(transparent)]
    Scope(#[from] ScopeError),
}

#[test]
fn ruleset_error_breaking_change() {
    use crate::*;

    // Generics are part of the API and modifying them can lead to a breaking change.
    let _: RulesetError = RulesetError::HandleAccesses(HandleAccessesError::Fs(
        HandleAccessError::Compat(CompatError::Access(AccessError::Empty)),
    ));
}

/// Identifies errors when updating the ruleset's handled access-rights.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum HandleAccessError<T>
where
    T: HandledAccess,
{
    #[error(transparent)]
    Compat(#[from] CompatError<T>),
}

/// Identifies errors when updating the ruleset's scopes.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ScopeError {
    #[error(transparent)]
    Compat(#[from] CompatError<Scope>),
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum HandleAccessesError {
    #[error(transparent)]
    Fs(HandleAccessError<AccessFs>),
    #[error(transparent)]
    Net(HandleAccessError<AccessNet>),
}

// Generically implement for all the handled access implementations rather than for the cases
// listed in HandleAccessesError (with #[from]).
impl<A> From<HandleAccessError<A>> for HandleAccessesError
where
    A: PrivateHandledAccess,
{
    fn from(error: HandleAccessError<A>) -> Self {
        A::into_handle_accesses_error(error)
    }
}

/// Identifies errors when creating a ruleset.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum CreateRulesetError {
    /// The `landlock_create_ruleset()` system call failed.
    #[error("failed to create a ruleset: {source}")]
    #[non_exhaustive]
    CreateRulesetCall { source: io::Error },
    /// Missing call to [`RulesetAttr::handle_access()`](crate::RulesetAttr::handle_access)
    /// or [`RulesetAttr::scope()`](crate::RulesetAttr::scope).
    #[error("missing access")]
    MissingHandledAccess,
}

/// Identifies errors when adding a rule to a ruleset.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AddRuleError<T>
where
    T: HandledAccess,
{
    /// The `landlock_add_rule()` system call failed.
    #[error("failed to add a rule: {source}")]
    #[non_exhaustive]
    AddRuleCall { source: io::Error },
    /// The rule's access-rights are not all handled by the (requested) ruleset access-rights.
    #[error("access-rights not handled by the ruleset: {incompatible:?}")]
    UnhandledAccess {
        access: BitFlags<T>,
        incompatible: BitFlags<T>,
    },
    #[error(transparent)]
    Compat(#[from] CompatError<T>),
}

// Generically implement for all the handled access implementations rather than for the cases listed
// in AddRulesError (with #[from]).
impl<A> From<AddRuleError<A>> for AddRulesError
where
    A: PrivateHandledAccess,
{
    fn from(error: AddRuleError<A>) -> Self {
        A::into_add_rules_error(error)
    }
}

/// Identifies errors when adding rules to a ruleset thanks to an iterator returning
/// Result<Rule, E> items.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AddRulesError {
    #[error(transparent)]
    Fs(AddRuleError<AccessFs>),
    #[error(transparent)]
    Net(AddRuleError<AccessNet>),
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum CompatError<T>
where
    T: Access,
{
    #[error(transparent)]
    PathBeneath(#[from] PathBeneathError),
    #[error(transparent)]
    Access(#[from] AccessError<T>),
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PathBeneathError {
    /// To check that access-rights are consistent with a file descriptor, a call to
    /// [`RulesetCreatedAttr::add_rule()`](crate::RulesetCreatedAttr::add_rule)
    /// looks at the file type with an `fstat()` system call.
    #[error("failed to check file descriptor type: {source}")]
    #[non_exhaustive]
    StatCall { source: io::Error },
    /// This error is returned by
    /// [`RulesetCreatedAttr::add_rule()`](crate::RulesetCreatedAttr::add_rule)
    /// if the related PathBeneath object is not set to best-effort,
    /// and if its allowed access-rights contain directory-only ones
    /// whereas the file descriptor doesn't point to a directory.
    #[error("incompatible directory-only access-rights: {incompatible:?}")]
    DirectoryAccess {
        access: BitFlags<AccessFs>,
        incompatible: BitFlags<AccessFs>,
    },
}

#[derive(Debug, Error)]
// Exhaustive enum
pub enum AccessError<T>
where
    T: Access,
{
    /// The access-rights set is empty, which doesn't make sense and would be rejected by the
    /// kernel.
    #[error("empty access-right")]
    Empty,
    /// The access-rights set was forged with the unsafe `BitFlags::from_bits_unchecked()` and it
    /// contains unknown bits.
    #[error("unknown access-rights (at build time): {unknown:?}")]
    Unknown {
        access: BitFlags<T>,
        unknown: BitFlags<T>,
    },
    /// The best-effort approach was (deliberately) disabled and the requested access-rights are
    /// fully incompatible with the running kernel.
    #[error("fully incompatible access-rights: {access:?}")]
    Incompatible { access: BitFlags<T> },
    /// The best-effort approach was (deliberately) disabled and the requested access-rights are
    /// partially incompatible with the running kernel.
    #[error("partially incompatible access-rights: {incompatible:?}")]
    PartiallyCompatible {
        access: BitFlags<T>,
        incompatible: BitFlags<T>,
    },
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RestrictSelfError {
    /// The `prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)` system call failed.
    #[error("failed to set no_new_privs: {source}")]
    #[non_exhaustive]
    SetNoNewPrivsCall { source: io::Error },
    /// The `landlock_restrict_self() `system call failed.
    #[error("failed to restrict the calling thread: {source}")]
    #[non_exhaustive]
    RestrictSelfCall { source: io::Error },
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PathFdError {
    /// The `open()` system call failed.
    #[error("failed to open \"{path}\": {source}")]
    #[non_exhaustive]
    OpenCall { source: io::Error, path: PathBuf },
}

#[cfg(test)]
#[derive(Debug, Error)]
pub(crate) enum TestRulesetError {
    #[error(transparent)]
    Ruleset(#[from] RulesetError),
    #[error(transparent)]
    PathFd(#[from] PathFdError),
    #[error(transparent)]
    File(#[from] std::io::Error),
}

/// Get the underlying errno value.
///
/// This helper is useful for FFI to easily translate a Landlock error into an
/// errno value.
#[derive(Debug, PartialEq, Eq)]
pub struct Errno(c_int);

impl Errno {
    pub fn new(value: c_int) -> Self {
        Self(value)
    }
}

impl<T> From<T> for Errno
where
    T: std::error::Error,
{
    fn from(error: T) -> Self {
        let default = libc::EINVAL;
        if let Some(e) = error.source() {
            if let Some(e) = e.downcast_ref::<std::io::Error>() {
                return Errno(e.raw_os_error().unwrap_or(default));
            }
        }
        Errno(default)
    }
}

impl std::ops::Deref for Errno {
    type Target = c_int;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[cfg(test)]
fn _test_ruleset_errno(expected_errno: c_int) {
    use std::io::Error;

    let handle_access_err = RulesetError::HandleAccesses(HandleAccessesError::Fs(
        HandleAccessError::Compat(CompatError::Access(AccessError::Empty)),
    ));
    assert_eq!(*Errno::from(handle_access_err), libc::EINVAL);

    let create_ruleset_err = RulesetError::CreateRuleset(CreateRulesetError::CreateRulesetCall {
        source: Error::from_raw_os_error(expected_errno),
    });
    assert_eq!(*Errno::from(create_ruleset_err), expected_errno);

    let add_rules_fs_err = RulesetError::AddRules(AddRulesError::Fs(AddRuleError::AddRuleCall {
        source: Error::from_raw_os_error(expected_errno),
    }));
    assert_eq!(*Errno::from(add_rules_fs_err), expected_errno);

    let add_rules_net_err = RulesetError::AddRules(AddRulesError::Net(AddRuleError::AddRuleCall {
        source: Error::from_raw_os_error(expected_errno),
    }));
    assert_eq!(*Errno::from(add_rules_net_err), expected_errno);

    let add_rules_other_err =
        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess {
            access: AccessFs::Execute.into(),
            incompatible: BitFlags::<AccessFs>::EMPTY,
        }));
    assert_eq!(*Errno::from(add_rules_other_err), libc::EINVAL);

    let restrict_self_err = RulesetError::RestrictSelf(RestrictSelfError::RestrictSelfCall {
        source: Error::from_raw_os_error(expected_errno),
    });
    assert_eq!(*Errno::from(restrict_self_err), expected_errno);

    let set_no_new_privs_err = RulesetError::RestrictSelf(RestrictSelfError::SetNoNewPrivsCall {
        source: Error::from_raw_os_error(expected_errno),
    });
    assert_eq!(*Errno::from(set_no_new_privs_err), expected_errno);

    let create_ruleset_missing_err =
        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess);
    assert_eq!(*Errno::from(create_ruleset_missing_err), libc::EINVAL);
}

#[test]
fn test_ruleset_errno() {
    _test_ruleset_errno(libc::EACCES);
    _test_ruleset_errno(libc::EIO);
}


================================================
FILE: src/fs.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::compat::private::OptionCompatLevelMut;
use crate::{
    uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState,
    Compatible, HandleAccessError, HandleAccessesError, HandledAccess, PathBeneathError,
    PathFdError, PrivateHandledAccess, PrivateRule, Rule, Ruleset, RulesetCreated, RulesetError,
    TailoredCompatLevel, TryCompat, ABI,
};
use enumflags2::{bitflags, make_bitflags, BitFlags};
use std::fs::OpenOptions;
use std::io::Error;
use std::mem::zeroed;
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
use std::path::Path;

#[cfg(test)]
use crate::{RulesetAttr, RulesetCreatedAttr};
#[cfg(test)]
use strum::IntoEnumIterator;

/// File system access right.
///
/// Each variant of `AccessFs` is an [access right](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights)
/// for the file system.
/// A set of access rights can be created with [`BitFlags<AccessFs>`](BitFlags).
///
/// # Example
///
/// ```
/// use landlock::{ABI, Access, AccessFs, BitFlags, make_bitflags};
///
/// let exec = AccessFs::Execute;
///
/// let exec_set: BitFlags<AccessFs> = exec.into();
///
/// let file_content = make_bitflags!(AccessFs::{Execute | WriteFile | ReadFile});
///
/// let fs_v1 = AccessFs::from_all(ABI::V1);
///
/// let without_exec = fs_v1 & !AccessFs::Execute;
///
/// assert_eq!(fs_v1 | AccessFs::Refer, AccessFs::from_all(ABI::V2));
/// ```
///
/// # Warning
///
/// To avoid unknown restrictions **don't use `BitFlags::<AccessFs>::all()` nor `BitFlags::ALL`**,
/// but use a version you tested and vetted instead,
/// for instance [`AccessFs::from_all(ABI::V1)`](Access::from_all).
/// Direct use of **the [`BitFlags`] API is deprecated**.
/// See [`ABI`] for the rationale and help to test it.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum AccessFs {
    /// Execute a file.
    Execute = uapi::LANDLOCK_ACCESS_FS_EXECUTE as u64,
    /// Open a file with write access.
    ///
    /// # Note
    ///
    /// Certain operations (such as [`std::fs::write`]) may also require [`AccessFs::Truncate`] since [`ABI::V3`].
    WriteFile = uapi::LANDLOCK_ACCESS_FS_WRITE_FILE as u64,
    /// Open a file with read access.
    ReadFile = uapi::LANDLOCK_ACCESS_FS_READ_FILE as u64,
    /// Open a directory or list its content.
    ReadDir = uapi::LANDLOCK_ACCESS_FS_READ_DIR as u64,
    /// Remove an empty directory or rename one.
    RemoveDir = uapi::LANDLOCK_ACCESS_FS_REMOVE_DIR as u64,
    /// Unlink (or rename) a file.
    RemoveFile = uapi::LANDLOCK_ACCESS_FS_REMOVE_FILE as u64,
    /// Create (or rename or link) a character device.
    MakeChar = uapi::LANDLOCK_ACCESS_FS_MAKE_CHAR as u64,
    /// Create (or rename) a directory.
    MakeDir = uapi::LANDLOCK_ACCESS_FS_MAKE_DIR as u64,
    /// Create (or rename or link) a regular file.
    MakeReg = uapi::LANDLOCK_ACCESS_FS_MAKE_REG as u64,
    /// Create (or rename or link) a UNIX domain socket.
    MakeSock = uapi::LANDLOCK_ACCESS_FS_MAKE_SOCK as u64,
    /// Create (or rename or link) a named pipe.
    MakeFifo = uapi::LANDLOCK_ACCESS_FS_MAKE_FIFO as u64,
    /// Create (or rename or link) a block device.
    MakeBlock = uapi::LANDLOCK_ACCESS_FS_MAKE_BLOCK as u64,
    /// Create (or rename or link) a symbolic link.
    MakeSym = uapi::LANDLOCK_ACCESS_FS_MAKE_SYM as u64,
    /// Link or rename a file from or to a different directory.
    Refer = uapi::LANDLOCK_ACCESS_FS_REFER as u64,
    /// Truncate a file with `truncate(2)`, `ftruncate(2)`, `creat(2)`, or `open(2)` with `O_TRUNC`.
    Truncate = uapi::LANDLOCK_ACCESS_FS_TRUNCATE as u64,
    /// Send IOCL commands to a device file.
    IoctlDev = uapi::LANDLOCK_ACCESS_FS_IOCTL_DEV as u64,
}

impl Access for AccessFs {
    /// Union of [`from_read()`](AccessFs::from_read) and [`from_write()`](AccessFs::from_write).
    fn from_all(abi: ABI) -> BitFlags<Self> {
        // An empty access-right would be an error if passed to the kernel, but because the kernel
        // doesn't support Landlock, no Landlock syscall should be called.  try_compat() should
        // also return RestrictionStatus::Unrestricted when called with unsupported/empty
        // access-rights.
        Self::from_read(abi) | Self::from_write(abi)
    }
}

impl AccessFs {
    // Roughly read (i.e. not all FS actions are handled).
    /// Gets the access rights identified as read-only according to a specific ABI.
    /// Exclusive with [`from_write()`](AccessFs::from_write).
    pub fn from_read(abi: ABI) -> BitFlags<Self> {
        match abi {
            ABI::Unsupported => BitFlags::EMPTY,
            ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 | ABI::V6 => make_bitflags!(AccessFs::{
                Execute
                | ReadFile
                | ReadDir
            }),
        }
    }

    // Roughly write (i.e. not all FS actions are handled).
    /// Gets the access rights identified as write-only according to a specific ABI.
    /// Exclusive with [`from_read()`](AccessFs::from_read).
    pub fn from_write(abi: ABI) -> BitFlags<Self> {
        match abi {
            ABI::Unsupported => BitFlags::EMPTY,
            ABI::V1 => make_bitflags!(AccessFs::{
                WriteFile
                | RemoveDir
                | RemoveFile
                | MakeChar
                | MakeDir
                | MakeReg
                | MakeSock
                | MakeFifo
                | MakeBlock
                | MakeSym
            }),
            ABI::V2 => Self::from_write(ABI::V1) | AccessFs::Refer,
            ABI::V3 | ABI::V4 => Self::from_write(ABI::V2) | AccessFs::Truncate,
            ABI::V5 | ABI::V6 => Self::from_write(ABI::V4) | AccessFs::IoctlDev,
        }
    }

    /// Gets the access rights legitimate for non-directory files.
    pub fn from_file(abi: ABI) -> BitFlags<Self> {
        Self::from_all(abi) & ACCESS_FILE
    }
}

#[test]
fn consistent_access_fs_rw() {
    for abi in ABI::iter() {
        let access_all = AccessFs::from_all(abi);
        let access_read = AccessFs::from_read(abi);
        let access_write = AccessFs::from_write(abi);
        let access_file = AccessFs::from_file(abi);
        assert_eq!(access_read, !access_write & access_all);
        assert_eq!(access_read | access_write, access_all);
        assert_eq!(access_file, access_all & ACCESS_FILE);
    }
}

impl HandledAccess for AccessFs {}

impl PrivateHandledAccess for AccessFs {
    fn ruleset_handle_access(
        ruleset: &mut Ruleset,
        access: BitFlags<Self>,
    ) -> Result<(), HandleAccessesError> {
        // We need to record the requested accesses for PrivateRule::check_consistency().
        ruleset.requested_handled_fs |= access;
        ruleset.actual_handled_fs |= match access
            .try_compat(
                ruleset.compat.abi(),
                ruleset.compat.level,
                &mut ruleset.compat.state,
            )
            .map_err(HandleAccessError::Compat)?
        {
            Some(a) => a,
            None => return Ok(()),
        };
        Ok(())
    }

    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
        AddRulesError::Fs(error)
    }

    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
        HandleAccessesError::Fs(error)
    }
}

// TODO: Make ACCESS_FILE a property of AccessFs.
// TODO: Add tests for ACCESS_FILE.
const ACCESS_FILE: BitFlags<AccessFs> = make_bitflags!(AccessFs::{
    ReadFile | WriteFile | Execute | Truncate | IoctlDev
});

// XXX: What should we do when a stat call failed?
fn is_file<F>(fd: F) -> Result<bool, Error>
where
    F: AsFd,
{
    unsafe {
        let mut stat = zeroed();
        match libc::fstat(fd.as_fd().as_raw_fd(), &mut stat) {
            0 => Ok((stat.st_mode & libc::S_IFMT) != libc::S_IFDIR),
            _ => Err(Error::last_os_error()),
        }
    }
}

/// Landlock rule for a file hierarchy.
///
/// # Example
///
/// ```
/// use landlock::{AccessFs, PathBeneath, PathFd, PathFdError};
///
/// fn home_dir() -> Result<PathBeneath<PathFd>, PathFdError> {
///     Ok(PathBeneath::new(PathFd::new("/home")?, AccessFs::ReadDir))
/// }
/// ```
#[derive(Debug)]
pub struct PathBeneath<F> {
    attr: uapi::landlock_path_beneath_attr,
    // Ties the lifetime of a file descriptor to this object.
    parent_fd: F,
    allowed_access: BitFlags<AccessFs>,
    compat_level: Option<CompatLevel>,
}

impl<F> PathBeneath<F>
where
    F: AsFd,
{
    /// Creates a new `PathBeneath` rule identifying the `parent` directory of a file hierarchy,
    /// or just a file, and allows `access` on it.
    /// The `parent` file descriptor will be automatically closed with the returned `PathBeneath`.
    pub fn new<A>(parent: F, access: A) -> Self
    where
        A: Into<BitFlags<AccessFs>>,
    {
        PathBeneath {
            // Invalid access rights until as_ptr() is called.
            attr: unsafe { zeroed() },
            parent_fd: parent,
            allowed_access: access.into(),
            compat_level: None,
        }
    }
}

impl<F> TryCompat<AccessFs> for PathBeneath<F>
where
    F: AsFd,
{
    fn try_compat_children<L>(
        mut self,
        abi: ABI,
        parent_level: L,
        compat_state: &mut CompatState,
    ) -> Result<Option<Self>, CompatError<AccessFs>>
    where
        L: Into<CompatLevel>,
    {
        // Checks with our own compatibility level, if any.
        self.allowed_access = match self.allowed_access.try_compat(
            abi,
            self.tailored_compat_level(parent_level),
            compat_state,
        )? {
            Some(a) => a,
            None => return Ok(None),
        };
        Ok(Some(self))
    }

    fn try_compat_inner(
        &mut self,
        _abi: ABI,
    ) -> Result<CompatResult<AccessFs>, CompatError<AccessFs>> {
        // Gets subset of valid accesses according the FD type.
        let valid_access =
            if is_file(&self.parent_fd).map_err(|e| PathBeneathError::StatCall { source: e })? {
                self.allowed_access & ACCESS_FILE
            } else {
                self.allowed_access
            };

        if self.allowed_access != valid_access {
            let error = PathBeneathError::DirectoryAccess {
                access: self.allowed_access,
                incompatible: self.allowed_access ^ valid_access,
            }
            .into();
            self.allowed_access = valid_access;
            // Linux would return EINVAL.
            Ok(CompatResult::Partial(error))
        } else {
            Ok(CompatResult::Full)
        }
    }
}

#[test]
fn path_beneath_try_compat_children() {
    use crate::*;

    // AccessFs::Refer is not handled by ABI::V1 and only for directories.
    let access_file = AccessFs::ReadFile | AccessFs::Refer;

    // Test error ordering with ABI::V1
    let mut ruleset = Ruleset::from(ABI::V1).handle_access(access_file).unwrap();
    // Do not actually perform any syscall.
    ruleset.compat.state = CompatState::Dummy;
    assert!(matches!(
        RulesetCreated::new(ruleset, None)
            .set_compatibility(CompatLevel::HardRequirement)
            .add_rule(PathBeneath::new(PathFd::new("/dev/null").unwrap(), access_file))
            .unwrap_err(),
        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::Compat(
            CompatError::PathBeneath(PathBeneathError::DirectoryAccess { access, incompatible })
        ))) if access == access_file && incompatible == AccessFs::Refer
    ));

    // Test error ordering with ABI::V2
    let mut ruleset = Ruleset::from(ABI::V2).handle_access(access_file).unwrap();
    // Do not actually perform any syscall.
    ruleset.compat.state = CompatState::Dummy;
    assert!(matches!(
        RulesetCreated::new(ruleset, None)
            .set_compatibility(CompatLevel::HardRequirement)
            .add_rule(PathBeneath::new(PathFd::new("/dev/null").unwrap(), access_file))
            .unwrap_err(),
        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::Compat(
            CompatError::PathBeneath(PathBeneathError::DirectoryAccess { access, incompatible })
        ))) if access == access_file && incompatible == AccessFs::Refer
    ));
}

#[test]
fn path_beneath_try_compat() {
    use crate::*;

    let abi = ABI::V1;

    for file in &["/etc/passwd", "/dev/null"] {
        let mut compat_state = CompatState::Init;
        let ro_access = AccessFs::ReadDir | AccessFs::ReadFile;
        assert!(matches!(
            PathBeneath::new(PathFd::new(file).unwrap(), ro_access)
                .try_compat(abi, CompatLevel::HardRequirement, &mut compat_state)
                .unwrap_err(),
            CompatError::PathBeneath(PathBeneathError::DirectoryAccess { access, incompatible })
                if access == ro_access && incompatible == AccessFs::ReadDir
        ));

        let mut compat_state = CompatState::Init;
        assert!(matches!(
            PathBeneath::new(PathFd::new(file).unwrap(), BitFlags::EMPTY)
                .try_compat(abi, CompatLevel::BestEffort, &mut compat_state)
                .unwrap_err(),
            CompatError::Access(AccessError::Empty)
        ));
    }

    let full_access = AccessFs::from_all(ABI::V1);
    for compat_level in &[
        CompatLevel::BestEffort,
        CompatLevel::SoftRequirement,
        CompatLevel::HardRequirement,
    ] {
        let mut compat_state = CompatState::Init;
        let mut path_beneath = PathBeneath::new(PathFd::new("/").unwrap(), full_access)
            .try_compat(abi, *compat_level, &mut compat_state)
            .unwrap()
            .unwrap();
        assert_eq!(compat_state, CompatState::Full);

        // Without synchronization.
        let raw_access = path_beneath.attr.allowed_access;
        assert_eq!(raw_access, 0);

        // Synchronize the inner attribute buffer.
        let _ = path_beneath.as_ptr();
        let raw_access = path_beneath.attr.allowed_access;
        assert_eq!(raw_access, full_access.bits());
    }
}

impl<F> OptionCompatLevelMut for PathBeneath<F> {
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
        &mut self.compat_level
    }
}

impl<F> OptionCompatLevelMut for &mut PathBeneath<F> {
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
        &mut self.compat_level
    }
}

impl<F> Compatible for PathBeneath<F> {}

impl<F> Compatible for &mut PathBeneath<F> {}

#[test]
fn path_beneath_compatibility() {
    let mut path = PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::from_all(ABI::V1));
    let path_ref = &mut path;

    let level = path_ref.as_option_compat_level_mut();
    assert_eq!(level, &None);
    assert_eq!(
        <Option<CompatLevel> as Into<CompatLevel>>::into(*level),
        CompatLevel::BestEffort
    );

    path_ref.set_compatibility(CompatLevel::SoftRequirement);
    assert_eq!(
        path_ref.as_option_compat_level_mut(),
        &Some(CompatLevel::SoftRequirement)
    );

    path.set_compatibility(CompatLevel::HardRequirement);
}

// It is useful for documentation generation to explicitely implement Rule for every types, instead
// of doing it generically.
impl<F> Rule<AccessFs> for PathBeneath<F> where F: AsFd {}

impl<F> PrivateRule<AccessFs> for PathBeneath<F>
where
    F: AsFd,
{
    const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH;

    fn as_ptr(&mut self) -> *const libc::c_void {
        self.attr.parent_fd = self.parent_fd.as_fd().as_raw_fd();
        self.attr.allowed_access = self.allowed_access.bits();
        &self.attr as *const _ as _
    }

    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {
        // Checks that this rule doesn't contain a superset of the access-rights handled by the
        // ruleset.  This check is about requested access-rights but not actual access-rights.
        // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel
        // (which is handled by Ruleset and RulesetCreated).
        if ruleset.requested_handled_fs.contains(self.allowed_access) {
            Ok(())
        } else {
            Err(AddRuleError::UnhandledAccess {
                access: self.allowed_access,
                incompatible: self.allowed_access & !ruleset.requested_handled_fs,
            }
            .into())
        }
    }
}

#[test]
fn path_beneath_check_consistency() {
    use crate::*;

    let ro_access = AccessFs::ReadDir | AccessFs::ReadFile;
    let rx_access = AccessFs::Execute | AccessFs::ReadFile;
    assert!(matches!(
        Ruleset::from(ABI::Unsupported)
            .handle_access(ro_access)
            .unwrap()
            .create()
            .unwrap()
            .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), rx_access))
            .unwrap_err(),
        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { access, incompatible }))
            if access == rx_access && incompatible == AccessFs::Execute
    ));
}

/// Simple helper to open a file or a directory with the `O_PATH` flag.
///
/// This is the recommended way to identify a path
/// and manage the lifetime of the underlying opened file descriptor.
/// Indeed, using other [`AsFd`] implementations such as [`File`] brings more complexity
/// and may lead to unexpected errors (e.g., denied access).
///
/// [`File`]: std::fs::File
///
/// # Example
///
/// ```
/// use landlock::{AccessFs, PathBeneath, PathFd, PathFdError};
///
/// fn allowed_root_dir(access: AccessFs) -> Result<PathBeneath<PathFd>, PathFdError> {
///     let fd = PathFd::new("/")?;
///     Ok(PathBeneath::new(fd, access))
/// }
/// ```
#[derive(Debug)]
pub struct PathFd {
    fd: OwnedFd,
}

impl PathFd {
    pub fn new<T>(path: T) -> Result<Self, PathFdError>
    where
        T: AsRef<Path>,
    {
        Ok(PathFd {
            fd: OpenOptions::new()
                .read(true)
                // If the O_PATH is not supported, it is automatically ignored (Linux < 2.6.39).
                .custom_flags(libc::O_PATH | libc::O_CLOEXEC)
                .open(path.as_ref())
                .map_err(|e| PathFdError::OpenCall {
                    source: e,
                    path: path.as_ref().into(),
                })?
                .into(),
        })
    }
}

impl AsFd for PathFd {
    fn as_fd(&self) -> BorrowedFd<'_> {
        self.fd.as_fd()
    }
}

#[test]
fn path_fd() {
    use std::fs::File;
    use std::io::Read;

    PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Execute);
    PathBeneath::new(File::open("/").unwrap(), AccessFs::Execute);

    let mut buffer = [0; 1];
    // Checks that PathFd really returns an FD opened with O_PATH (Bad file descriptor error).
    File::from(PathFd::new("/etc/passwd").unwrap().fd)
        .read(&mut buffer)
        .unwrap_err();
}

/// Helper to quickly create an iterator of PathBeneath rules.
///
/// # Note
///
/// From the kernel's perspective, Landlock rules operate on file descriptors, not paths.
/// This is a helper to create rules based on paths. Here, `path_beneath_rules()` silently ignores
/// paths that cannot be opened, hence making the obtainment of a file descriptor impossible. When
/// possible and for a given path, `path_beneath_rules()` automatically adjusts [access rights](`AccessFs`),
/// depending on whether a directory or a file is present at that said path.
///
/// This behavior is the result of [`CompatLevel::BestEffort`], which is the default compatibility level of
/// all created rulesets. Thus, it applies to the example below. However, if [`CompatLevel::HardRequirement`]
/// is set using [`Compatible::set_compatibility`], attempting to create an incompatible rule at runtime will cause
/// this crate to raise an error instead.
///
/// # Example
///
/// ```
/// use landlock::{
///     ABI, Access, AccessFs, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus, RulesetError,
///     path_beneath_rules,
/// };
///
/// fn restrict_thread() -> Result<(), RulesetError> {
///     let abi = ABI::V1;
///     let status = Ruleset::default()
///         .handle_access(AccessFs::from_all(abi))?
///         .create()?
///         // Read-only access to /usr, /etc and /dev.
///         .add_rules(path_beneath_rules(&["/usr", "/etc", "/dev"], AccessFs::from_read(abi)))?
///         // Read-write access to /home and /tmp.
///         .add_rules(path_beneath_rules(&["/home", "/tmp"], AccessFs::from_all(abi)))?
///         .restrict_self()?;
///     match status.ruleset {
///         // The FullyEnforced case must be tested by the developer.
///         RulesetStatus::FullyEnforced => println!("Fully sandboxed."),
///         RulesetStatus::PartiallyEnforced => println!("Partially sandboxed."),
///         // Users should be warned that they are not protected.
///         RulesetStatus::NotEnforced => println!("Not sandboxed! Please update your kernel."),
///     }
///     Ok(())
/// }
/// ```
pub fn path_beneath_rules<I, P, A>(
    paths: I,
    access: A,
) -> impl Iterator<Item = Result<PathBeneath<PathFd>, RulesetError>>
where
    I: IntoIterator<Item = P>,
    P: AsRef<Path>,
    A: Into<BitFlags<AccessFs>>,
{
    let access = access.into();
    paths.into_iter().filter_map(move |p| match PathFd::new(p) {
        Ok(f) => {
            let valid_access = match is_file(&f) {
                Ok(true) => access & ACCESS_FILE,
                // If the stat call failed, let's blindly rely on the requested access rights.
                Err(_) | Ok(false) => access,
            };
            Some(Ok(PathBeneath::new(f, valid_access)))
        }
        Err(_) => None,
    })
}

#[test]
fn path_beneath_rules_iter() {
    let _ = Ruleset::default()
        .handle_access(AccessFs::from_all(ABI::V1))
        .unwrap()
        .create()
        .unwrap()
        .add_rules(path_beneath_rules(
            &["/usr", "/opt", "/does-not-exist", "/root"],
            AccessFs::Execute,
        ))
        .unwrap();
}


================================================
FILE: src/lib.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Landlock is a security feature available since Linux 5.13.
//! The 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.
//! This kind of sandbox is expected to help mitigate the security impact of bugs,
//! unexpected or malicious behaviors in applications.
//! Landlock empowers any process, including unprivileged ones, to securely restrict themselves.
//! More information about Landlock can be found in the [official website](https://landlock.io).
//!
//! This crate provides a safe abstraction for the Landlock system calls, along with some helpers.
//!
//! Minimum Supported Rust Version (MSRV): 1.71
//!
//! # Use cases
//!
//! This crate is especially useful to protect users' data by sandboxing:
//! * trusted applications dealing with potentially malicious data
//!   (e.g., complex file format, network request) that could exploit security vulnerabilities;
//! * sandbox managers, container runtimes or shells launching untrusted applications.
//!
//! # Examples
//!
//! A simple example can be found with the [`path_beneath_rules()`] helper.
//! More complex examples can be found with the [`Ruleset` documentation](Ruleset)
//! and the [sandboxer example](https://github.com/landlock-lsm/rust-landlock/blob/master/examples/sandboxer.rs).
//!
//! # Current limitations
//!
//! This crate exposes the Landlock features available as of Linux 5.19
//! and then inherits some [kernel limitations](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#current-limitations)
//! that will be addressed with future kernel releases
//! (e.g., arbitrary mounts are always denied).
//!
//! # Compatibility
//!
//! Types defined in this crate are designed to enable the strictest Landlock configuration
//! for the given kernel on which the program runs.
//! In the default [best-effort](CompatLevel::BestEffort) mode,
//! [`Ruleset`] will determine compatibility
//! with the intersection of the currently running kernel's features
//! and those required by the caller.
//! This way, callers can distinguish between
//! Landlock compatibility issues inherent to the current system
//! (e.g., file names that don't exist)
//! and misconfiguration that should be fixed in the program
//! (e.g., empty or inconsistent access rights).
//! [`RulesetError`] identifies such kind of errors.
//!
//! With [`set_compatibility(CompatLevel::BestEffort)`](Compatible::set_compatibility),
//! users of the crate may mark Landlock features that are deemed required
//! and other features that may be downgraded to use lower security on systems
//! where they can't be enforced.
//! It is discouraged to compare the system's provided [Landlock ABI](ABI) version directly,
//! as it is difficult to track detailed ABI differences
//! which are handled thanks to the [`Compatible`] trait.
//!
//! To make it easier to migrate to a new version of this library,
//! we use the builder pattern
//! and designed objects to require the minimal set of method arguments.
//! Most `enum` are marked as `non_exhaustive` to enable backward-compatible evolutions.
//!
//! ## Test strategy
//!
//! Developers should test their sandboxed applications
//! with a kernel that supports all requested Landlock features
//! and check that [`RulesetCreated::restrict_self()`] returns a status matching
//! [`Ok(RestrictionStatus { ruleset: RulesetStatus::FullyEnforced, no_new_privs: true, })`](RestrictionStatus)
//! to make sure everything works as expected in an enforced sandbox.
//! Alternatively, using [`set_compatibility(CompatLevel::HardRequirement)`](Compatible::set_compatibility)
//! will immediately inform about unsupported Landlock features.
//! These configurations should only depend on the test environment
//! (e.g. [by checking an environment variable](https://github.com/landlock-lsm/rust-landlock/search?q=LANDLOCK_CRATE_TEST_ABI)).
//! However, applications should only check that no error is returned (i.e. `Ok(_)`)
//! and optionally log and inform users that the application is not fully sandboxed
//! because of missing features from the running kernel.

#[cfg(test)]
#[macro_use]
extern crate lazy_static;

pub use access::{Access, HandledAccess};
pub use compat::{CompatLevel, Compatible, LandlockStatus, ABI};
pub use enumflags2::{make_bitflags, BitFlags};
pub use errata::Erratum;
pub use errors::{
    AccessError, AddRuleError, AddRulesError, CompatError, CreateRulesetError, Errno,
    HandleAccessError, HandleAccessesError, PathBeneathError, PathFdError, RestrictSelfError,
    RulesetError, ScopeError,
};
pub use fs::{path_beneath_rules, AccessFs, PathBeneath, PathFd};
pub use net::{AccessNet, NetPort};
pub use ruleset::{
    RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr,
    RulesetStatus,
};
pub use scope::Scope;

use access::PrivateHandledAccess;
use compat::{CompatResult, CompatState, Compatibility, TailoredCompatLevel, TryCompat};
use ruleset::PrivateRule;

#[cfg(test)]
use compat::{can_emulate, get_errno_from_landlock_status};
#[cfg(test)]
use errors::TestRulesetError;
#[cfg(test)]
use strum::IntoEnumIterator;

mod access;
mod compat;
mod errata;
mod errors;
mod fs;
mod net;
mod ruleset;
mod scope;
mod uapi;

// Makes sure private traits cannot be implemented outside of this crate.
mod private {
    pub trait Sealed {}

    impl Sealed for crate::AccessFs {}
    impl Sealed for crate::AccessNet {}
    impl Sealed for crate::Scope {}
}

#[cfg(test)]
mod tests {
    use crate::*;

    // Emulate old kernel supports.
    fn check_ruleset_support<F>(
        partial: ABI,
        full: Option<ABI>,
        check: F,
        error_if_abi_lt_partial: bool,
    ) where
        F: Fn(Ruleset) -> Result<RestrictionStatus, TestRulesetError> + Send + Copy + 'static,
    {
        // If there is no partial support, it means that `full == partial`.
        assert!(partial <= full.unwrap_or(partial));
        for abi in ABI::iter() {
            // Ensures restrict_self() is called on a dedicated thread to avoid inconsistent tests.
            let ret = std::thread::spawn(move || check(Ruleset::from(abi)))
                .join()
                .unwrap();

            // Useful for failed tests and with cargo test -- --show-output
            println!("Checking ABI {abi:?}: received {ret:#?}");
            if can_emulate(abi, partial, full) {
                if abi < partial && error_if_abi_lt_partial {
                    // TODO: Check exact error type; this may require better error types.
                    assert!(matches!(ret, Err(TestRulesetError::Ruleset(_))));
                } else {
                    let full_support = if let Some(full_inner) = full {
                        abi >= full_inner
                    } else {
                        false
                    };
                    let ruleset_status = if full_support {
                        RulesetStatus::FullyEnforced
                    } else if abi >= partial {
                        RulesetStatus::PartiallyEnforced
                    } else {
                        RulesetStatus::NotEnforced
                    };
                    let landlock_status = abi.into();
                    println!("Expecting ruleset status {ruleset_status:?}");
                    println!("Expecting Landlock status {landlock_status:?}");
                    assert!(matches!(
                        ret,
                        Ok(RestrictionStatus {
                            ruleset,
                            landlock,
                            no_new_privs: true,
                        }) if ruleset == ruleset_status && landlock == landlock_status
                    ))
                }
            } else {
                // The errno value should be ENOSYS, EOPNOTSUPP, EINVAL (e.g. when an unknown
                // access right is provided), or E2BIG (e.g. when there is an unknown field in a
                // Landlock syscall attribute).
                let errno = get_errno_from_landlock_status();
                println!("Expecting error {errno:?}");
                match ret {
                    Err(
                        ref error @ TestRulesetError::Ruleset(RulesetError::CreateRuleset(
                            CreateRulesetError::CreateRulesetCall { ref source },
                        )),
                    ) => {
                        assert_eq!(source.raw_os_error(), Some(*Errno::from(error)));
                        match (source.raw_os_error(), errno) {
                            (Some(e1), Some(e2)) => assert_eq!(e1, e2),
                            (Some(e1), None) => assert!(matches!(e1, libc::EINVAL | libc::E2BIG)),
                            _ => unreachable!(),
                        }
                    }
                    _ => unreachable!(),
                }
            }
        }
    }

    #[test]
    fn allow_root_compat() {
        let abi = ABI::V1;

        check_ruleset_support(
            abi,
            Some(abi),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::from_all(abi))?
                    .create()?
                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::from_all(abi)))?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn too_much_access_rights_for_a_file() {
        let abi = ABI::V1;

        check_ruleset_support(
            abi,
            Some(abi),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::from_all(abi))?
                    .create()?
                    // Same code as allow_root_compat() but with /etc/passwd instead of /
                    .add_rule(PathBeneath::new(
                        PathFd::new("/etc/passwd")?,
                        // Only allow legitimate access rights on a file.
                        AccessFs::from_file(abi),
                    ))?
                    .restrict_self()?)
            },
            false,
        );

        check_ruleset_support(
            abi,
            None,
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::from_all(abi))?
                    .create()?
                    // Same code as allow_root_compat() but with /etc/passwd instead of /
                    .add_rule(PathBeneath::new(
                        PathFd::new("/etc/passwd")?,
                        // Tries to allow all access rights on a file.
                        AccessFs::from_all(abi),
                    ))?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn path_beneath_rules_with_too_much_access_rights_for_a_file() {
        let abi = ABI::V1;

        check_ruleset_support(
            abi,
            Some(abi),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::from_all(ABI::V1))?
                    .create()?
                    // Same code as too_much_access_rights_for_a_file() but using path_beneath_rules()
                    .add_rules(path_beneath_rules(["/etc/passwd"], AccessFs::from_all(abi)))?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn allow_root_fragile() {
        let abi = ABI::V1;

        check_ruleset_support(
            abi,
            Some(abi),
            move |ruleset: Ruleset| -> _ {
                // Sets default support requirement: abort the whole sandboxing for any Landlock error.
                Ok(ruleset
                    // Must have at least the execute check…
                    .set_compatibility(CompatLevel::HardRequirement)
                    .handle_access(AccessFs::Execute)?
                    // …and possibly others.
                    .set_compatibility(CompatLevel::BestEffort)
                    .handle_access(AccessFs::from_all(abi))?
                    .create()?
                    .set_no_new_privs(true)
                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::from_all(abi)))?
                    .restrict_self()?)
            },
            true,
        );
    }

    #[test]
    fn ruleset_enforced() {
        let abi = ABI::V1;

        check_ruleset_support(
            abi,
            Some(abi),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    // Restricting without rule exceptions is legitimate to forbid a set of actions.
                    .handle_access(AccessFs::Execute)?
                    .create()?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn abi_v2_exec_refer() {
        check_ruleset_support(
            ABI::V1,
            Some(ABI::V2),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::Execute)?
                    // AccessFs::Refer is not supported by ABI::V1 (best-effort).
                    .handle_access(AccessFs::Refer)?
                    .create()?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn abi_v2_refer_only() {
        // When no access is handled, do not try to create a ruleset without access.
        check_ruleset_support(
            ABI::V2,
            Some(ABI::V2),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::Refer)?
                    .create()?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn abi_v3_truncate() {
        check_ruleset_support(
            ABI::V2,
            Some(ABI::V3),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::Refer)?
                    .handle_access(AccessFs::Truncate)?
                    .create()?
                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::Refer))?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn ruleset_created_try_clone() {
        check_ruleset_support(
            ABI::V1,
            Some(ABI::V1),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::Execute)?
                    .create()?
                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::Execute))?
                    .try_clone()?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn abi_v4_tcp() {
        check_ruleset_support(
            ABI::V3,
            Some(ABI::V4),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::Truncate)?
                    .handle_access(AccessNet::BindTcp | AccessNet::ConnectTcp)?
                    .create()?
                    .add_rule(NetPort::new(1, AccessNet::ConnectTcp))?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn abi_v5_ioctl_dev() {
        check_ruleset_support(
            ABI::V4,
            Some(ABI::V5),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessNet::BindTcp)?
                    .handle_access(AccessFs::IoctlDev)?
                    .create()?
                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::IoctlDev))?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn abi_v6_scope_mix() {
        check_ruleset_support(
            ABI::V5,
            Some(ABI::V6),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .handle_access(AccessFs::IoctlDev)?
                    .scope(Scope::AbstractUnixSocket | Scope::Signal)?
                    .create()?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn abi_v6_scope_only() {
        check_ruleset_support(
            ABI::V6,
            Some(ABI::V6),
            move |ruleset: Ruleset| -> _ {
                Ok(ruleset
                    .scope(Scope::AbstractUnixSocket | Scope::Signal)?
                    .create()?
                    .restrict_self()?)
            },
            false,
        );
    }

    #[test]
    fn ruleset_created_try_clone_ownedfd() {
        use std::os::unix::io::{AsRawFd, OwnedFd};

        let abi = ABI::V1;
        check_ruleset_support(
            abi,
            Some(abi),
            move |ruleset: Ruleset| -> _ {
                let ruleset1 = ruleset.handle_access(AccessFs::from_all(abi))?.create()?;
                let ruleset2 = ruleset1.try_clone().unwrap();
                let ruleset3 = ruleset2.try_clone().unwrap();

                let some1: Option<OwnedFd> = ruleset1.into();
                if let Some(fd1) = some1 {
                    assert!(fd1.as_raw_fd() >= 0);

                    let some2: Option<OwnedFd> = ruleset2.into();
                    let fd2 = some2.unwrap();
                    assert!(fd2.as_raw_fd() >= 0);

                    assert_ne!(fd1.as_raw_fd(), fd2.as_raw_fd());
                }
                Ok(ruleset3.restrict_self()?)
            },
            false,
        );
    }
}


================================================
FILE: src/net.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::compat::private::OptionCompatLevelMut;
use crate::{
    uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState,
    Compatible, HandleAccessError, HandleAccessesError, HandledAccess, PrivateHandledAccess,
    PrivateRule, Rule, Ruleset, RulesetCreated, TailoredCompatLevel, TryCompat, ABI,
};
use enumflags2::{bitflags, BitFlags};
use std::mem::zeroed;

/// Network access right.
///
/// Each variant of `AccessNet` is an [access right](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights)
/// for the network.
/// A set of access rights can be created with [`BitFlags<AccessNet>`](BitFlags).
///
/// # Example
///
/// ```
/// use landlock::{ABI, Access, AccessNet, BitFlags, make_bitflags};
///
/// let bind = AccessNet::BindTcp;
///
/// let bind_set: BitFlags<AccessNet> = bind.into();
///
/// let bind_connect = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
///
/// let net_v4 = AccessNet::from_all(ABI::V4);
///
/// assert_eq!(bind_connect, net_v4);
/// ```
///
/// # Warning
///
/// To avoid unknown restrictions **don't use `BitFlags::<AccessNet>::all()` nor `BitFlags::ALL`**,
/// but use a version you tested and vetted instead,
/// for instance [`AccessNet::from_all(ABI::V4)`](Access::from_all).
/// Direct use of **the [`BitFlags`] API is deprecated**.
/// See [`ABI`] for the rationale and help to test it.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum AccessNet {
    /// Bind to a TCP port.
    BindTcp = uapi::LANDLOCK_ACCESS_NET_BIND_TCP as u64,
    /// Connect to a TCP port.
    ConnectTcp = uapi::LANDLOCK_ACCESS_NET_CONNECT_TCP as u64,
}

/// # Warning
///
/// If `ABI <= ABI::V3`, `AccessNet::from_all()` returns an empty `BitFlags<AccessNet>`, which
/// makes `Ruleset::handle_access(AccessNet::from_all(ABI::V3))` return an error.
impl Access for AccessNet {
    fn from_all(abi: ABI) -> BitFlags<Self> {
        match abi {
            ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 => BitFlags::EMPTY,
            ABI::V4 | ABI::V5 | ABI::V6 => AccessNet::BindTcp | AccessNet::ConnectTcp,
        }
    }
}

impl HandledAccess for AccessNet {}

impl PrivateHandledAccess for AccessNet {
    fn ruleset_handle_access(
        ruleset: &mut Ruleset,
        access: BitFlags<Self>,
    ) -> Result<(), HandleAccessesError> {
        // We need to record the requested accesses for PrivateRule::check_consistency().
        ruleset.requested_handled_net |= access;
        ruleset.actual_handled_net |= match access
            .try_compat(
                ruleset.compat.abi(),
                ruleset.compat.level,
                &mut ruleset.compat.state,
            )
            .map_err(HandleAccessError::Compat)?
        {
            Some(a) => a,
            None => return Ok(()),
        };
        Ok(())
    }

    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
        AddRulesError::Net(error)
    }

    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
        HandleAccessesError::Net(error)
    }
}

/// Landlock rule for a network port.
///
/// # Example
///
/// ```
/// use landlock::{AccessNet, NetPort};
///
/// fn bind_http() -> NetPort {
///     NetPort::new(80, AccessNet::BindTcp)
/// }
/// ```
#[derive(Debug)]
pub struct NetPort {
    attr: uapi::landlock_net_port_attr,
    // Only 16-bit port make sense for now.
    port: u16,
    allowed_access: BitFlags<AccessNet>,
    compat_level: Option<CompatLevel>,
}

// If we need support for 32 or 64 ports, we'll add a new_32() or a new_64() method returning a
// Result with a potential overflow error.
impl NetPort {
    /// Creates a new TCP port rule.
    ///
    /// As defined by the Linux ABI, `port` with a value of `0` means that TCP bindings will be
    /// allowed for a port range defined by `/proc/sys/net/ipv4/ip_local_port_range`.
    pub fn new<A>(port: u16, access: A) -> Self
    where
        A: Into<BitFlags<AccessNet>>,
    {
        NetPort {
            // Invalid access-rights until as_ptr() is called.
            attr: unsafe { zeroed() },
            port,
            allowed_access: access.into(),
            compat_level: None,
        }
    }
}

impl Rule<AccessNet> for NetPort {}

impl PrivateRule<AccessNet> for NetPort {
    const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_NET_PORT;

    fn as_ptr(&mut self) -> *const libc::c_void {
        self.attr.port = self.port as u64;
        self.attr.allowed_access = self.allowed_access.bits();
        &self.attr as *const _ as _
    }

    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {
        // Checks that this rule doesn't contain a superset of the access-rights handled by the
        // ruleset.  This check is about requested access-rights but not actual access-rights.
        // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel
        // (which is handled by Ruleset and RulesetCreated).
        if ruleset.requested_handled_net.contains(self.allowed_access) {
            Ok(())
        } else {
            Err(AddRuleError::UnhandledAccess {
                access: self.allowed_access,
                incompatible: self.allowed_access & !ruleset.requested_handled_net,
            }
            .into())
        }
    }
}

#[test]
fn net_port_check_consistency() {
    use crate::*;

    let bind = AccessNet::BindTcp;
    let bind_connect = bind | AccessNet::ConnectTcp;

    assert!(matches!(
        Ruleset::from(ABI::Unsupported)
            .handle_access(bind)
            .unwrap()
            .create()
            .unwrap()
            .add_rule(NetPort::new(1, bind_connect))
            .unwrap_err(),
        RulesetError::AddRules(AddRulesError::Net(AddRuleError::UnhandledAccess { access, incompatible }))
            if access == bind_connect && incompatible == AccessNet::ConnectTcp
    ));
}

impl TryCompat<AccessNet> for NetPort {
    fn try_compat_children<L>(
        mut self,
        abi: ABI,
        parent_level: L,
        compat_state: &mut CompatState,
    ) -> Result<Option<Self>, CompatError<AccessNet>>
    where
        L: Into<CompatLevel>,
    {
        // Checks with our own compatibility level, if any.
        self.allowed_access = match self.allowed_access.try_compat(
            abi,
            self.tailored_compat_level(parent_level),
            compat_state,
        )? {
            Some(a) => a,
            None => return Ok(None),
        };
        Ok(Some(self))
    }

    fn try_compat_inner(
        &mut self,
        _abi: ABI,
    ) -> Result<CompatResult<AccessNet>, CompatError<AccessNet>> {
        Ok(CompatResult::Full)
    }
}

impl OptionCompatLevelMut for NetPort {
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
        &mut self.compat_level
    }
}

impl OptionCompatLevelMut for &mut NetPort {
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
        &mut self.compat_level
    }
}

impl Compatible for NetPort {}

impl Compatible for &mut NetPort {}


================================================
FILE: src/ruleset.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::compat::private::OptionCompatLevelMut;
use crate::{
    uapi, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel, CompatState,
    Compatibility, Compatible, CreateRulesetError, HandledAccess, LandlockStatus,
    PrivateHandledAccess, RestrictSelfError, RulesetError, Scope, ScopeError, TryCompat,
};
use std::io::Error;
use std::mem::size_of_val;
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};

#[cfg(test)]
use crate::*;

// Public interface without methods and which is impossible to implement outside this crate.
pub trait Rule<T>: PrivateRule<T>
where
    T: HandledAccess,
{
}

// PrivateRule is not public outside this crate.
pub trait PrivateRule<T>
where
    Self: TryCompat<T> + Compatible,
    T: HandledAccess,
{
    const TYPE_ID: uapi::landlock_rule_type;

    /// Returns a raw pointer to the rule's inner attribute.
    ///
    /// The caller must ensure that the rule outlives the pointer this function returns, or else it
    /// will end up pointing to garbage.
    fn as_ptr(&mut self) -> *const libc::c_void;

    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError>;
}

/// Enforcement status of a ruleset.
#[derive(Debug, PartialEq, Eq)]
pub enum RulesetStatus {
    /// All requested restrictions are enforced.
    FullyEnforced,
    /// Some requested restrictions are enforced,
    /// following a best-effort approach.
    PartiallyEnforced,
    /// The running system doesn't support Landlock
    /// or a subset of the requested Landlock features.
    NotEnforced,
}

impl From<CompatState> for RulesetStatus {
    fn from(state: CompatState) -> Self {
        match state {
            CompatState::Init | CompatState::No | CompatState::Dummy => RulesetStatus::NotEnforced,
            CompatState::Full => RulesetStatus::FullyEnforced,
            CompatState::Partial => RulesetStatus::PartiallyEnforced,
        }
    }
}

// The Debug, PartialEq and Eq implementations are useful for crate users to debug and check the
// result of a Landlock ruleset enforcement.
/// Status of a [`RulesetCreated`]
/// after calling [`restrict_self()`](RulesetCreated::restrict_self).
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct RestrictionStatus {
    /// Status of the Landlock ruleset enforcement.
    pub ruleset: RulesetStatus,
    /// Status of `prctl(2)`'s `PR_SET_NO_NEW_PRIVS` enforcement.
    pub no_new_privs: bool,
    /// Status of Landlock for the running kernel.
    pub landlock: LandlockStatus,
}

fn prctl_set_no_new_privs() -> Result<(), Error> {
    match unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } {
        0 => Ok(()),
        _ => Err(Error::last_os_error()),
    }
}

fn support_no_new_privs() -> bool {
    // Only Linux < 3.5 or kernel with seccomp filters should return an error.
    matches!(
        unsafe { libc::prctl(libc::PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) },
        0 | 1
    )
}

/// Landlock ruleset builder.
///
/// `Ruleset` enables to create a Landlock ruleset in a flexible way
/// following the builder pattern.
/// Most build steps return a [`Result`] with [`RulesetError`].
///
/// You should probably not create more than one ruleset per application.
/// Creating multiple rulesets is only useful when gradually restricting an application
/// (e.g., a first set of generic restrictions before reading any file,
/// then a second set of tailored restrictions after reading the configuration).
///
/// # Simple example
///
/// Simple helper handling only Landlock-related errors.
///
/// ```
/// use landlock::{
///     Access, AccessFs, PathBeneath, PathFd, RestrictionStatus, Ruleset, RulesetAttr,
///     RulesetCreatedAttr, RulesetError, ABI,
/// };
/// use std::os::unix::io::AsFd;
///
/// fn restrict_fd<T>(hierarchy: T) -> Result<RestrictionStatus, RulesetError>
/// where
///     T: AsFd,
/// {
///     // The Landlock ABI should be incremented (and tested) regularly.
///     let abi = ABI::V1;
///     let access_all = AccessFs::from_all(abi);
///     let access_read = AccessFs::from_read(abi);
///     Ok(Ruleset::default()
///         .handle_access(access_all)?
///         .create()?
///         .add_rule(PathBeneath::new(hierarchy, access_read))?
///         .restrict_self()?)
/// }
///
/// let fd = PathFd::new("/home").expect("failed to open /home");
/// let status = restrict_fd(fd).expect("failed to build the ruleset");
/// ```
///
/// # Generic example
///
/// More generic helper handling a set of file hierarchies
/// and multiple types of error (i.e. [`RulesetError`](crate::RulesetError)
/// and [`PathFdError`](crate::PathFdError).
///
/// ```
/// use landlock::{
///     Access, AccessFs, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
///     RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
/// };
/// use thiserror::Error;
///
/// #[derive(Debug, Error)]
/// enum MyRestrictError {
///     #[error(transparent)]
///     Ruleset(#[from] RulesetError),
///     #[error(transparent)]
///     AddRule(#[from] PathFdError),
/// }
///
/// fn restrict_paths(hierarchies: &[&str]) -> Result<RestrictionStatus, MyRestrictError> {
///     // The Landlock ABI should be incremented (and tested) regularly.
///     let abi = ABI::V1;
///     let access_all = AccessFs::from_all(abi);
///     let access_read = AccessFs::from_read(abi);
///     Ok(Ruleset::default()
///         .handle_access(access_all)?
///         .create()?
///         .add_rules(
///             hierarchies
///                 .iter()
///                 .map::<Result<_, MyRestrictError>, _>(|p| {
///                     Ok(PathBeneath::new(PathFd::new(p)?, access_read))
///                 }),
///         )?
///         .restrict_self()?)
/// }
///
/// let status = restrict_paths(&["/usr", "/home"]).expect("failed to build the ruleset");
/// ```
#[derive(Debug)]
pub struct Ruleset {
    pub(crate) requested_handled_fs: BitFlags<AccessFs>,
    pub(crate) requested_handled_net: BitFlags<AccessNet>,
    pub(crate) requested_scoped: BitFlags<Scope>,
    pub(crate) actual_handled_fs: BitFlags<AccessFs>,
    pub(crate) actual_handled_net: BitFlags<AccessNet>,
    pub(crate) actual_scoped: BitFlags<Scope>,
    pub(crate) compat: Compatibility,
}

impl From<Compatibility> for Ruleset {
    fn from(compat: Compatibility) -> Self {
        Ruleset {
            // Non-working default handled FS accesses to force users to set them explicitely.
            requested_handled_fs: Default::default(),
            requested_handled_net: Default::default(),
            requested_scoped: Default::default(),
            actual_handled_fs: Default::default(),
            actual_handled_net: Default::default(),
            actual_scoped: Default::default(),
            compat,
        }
    }
}

#[cfg(test)]
impl From<ABI> for Ruleset {
    fn from(abi: ABI) -> Self {
        Ruleset::from(Compatibility::from(abi))
    }
}

#[test]
fn ruleset_add_rule_iter() {
    assert!(matches!(
        Ruleset::from(ABI::Unsupported)
            .handle_access(AccessFs::Execute)
            .unwrap()
            .create()
            .unwrap()
            .add_rule(PathBeneath::new(
                PathFd::new("/").unwrap(),
                AccessFs::ReadFile
            ))
            .unwrap_err(),
        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
    ));
}

impl Default for Ruleset {
    /// Returns a new `Ruleset`.
    /// This call automatically probes the running kernel to know if it supports Landlock.
    ///
    /// To be able to successfully call [`create()`](Ruleset::create),
    /// it is required to set the handled accesses with
    /// [`handle_access()`](Ruleset::handle_access).
    fn default() -> Self {
        // The API should be future-proof: one Rust program or library should have the same
        // behavior if built with an old or a newer crate (e.g. with an extended ruleset_attr
        // enum).  It should then not be possible to give an "all-possible-handled-accesses" to the
        // Ruleset builder because this value would be relative to the running kernel.
        Compatibility::new().into()
    }
}

impl Ruleset {
    #[allow(clippy::new_without_default)]
    #[deprecated(note = "Use Ruleset::default() instead")]
    pub fn new() -> Self {
        Ruleset::default()
    }

    /// Attempts to create a real Landlock ruleset (if supported by the running kernel).
    /// The returned [`RulesetCreated`] is also a builder.
    ///
    /// On error, returns a wrapped [`CreateRulesetError`].
    pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
        let body = || -> Result<RulesetCreated, CreateRulesetError> {
            match self.compat.state {
                CompatState::Init => {
                    // Checks that there is at least one requested access (e.g.
                    // requested_handled_fs): one call to handle_access().
                    Err(CreateRulesetError::MissingHandledAccess)
                }
                CompatState::No | CompatState::Dummy => {
                    // There is at least one requested access.
                    #[cfg(test)]
                    assert!(
                        !self.requested_handled_fs.is_empty()
                            || !self.requested_handled_net.is_empty()
                            || !self.requested_scoped.is_empty()
                    );

                    // CompatState::No should be handled as CompatState::Dummy because it is not
                    // possible to create an actual ruleset.
                    self.compat.update(CompatState::Dummy);
                    match self.compat.level.into() {
                        CompatLevel::HardRequirement => {
                            Err(CreateRulesetError::MissingHandledAccess)
                        }
                        _ => Ok(RulesetCreated::new(self, None)),
                    }
                }
                CompatState::Full | CompatState::Partial => {
                    // There is at least one actual handled access.
                    #[cfg(test)]
                    assert!(
                        !self.actual_handled_fs.is_empty()
                            || !self.actual_handled_net.is_empty()
                            || !self.actual_scoped.is_empty()
                    );

                    let attr = uapi::landlock_ruleset_attr {
                        handled_access_fs: self.actual_handled_fs.bits(),
                        handled_access_net: self.actual_handled_net.bits(),
                        scoped: self.actual_scoped.bits(),
                    };
                    match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {
                        fd if fd >= 0 => Ok(RulesetCreated::new(
                            self,
                            Some(unsafe { OwnedFd::from_raw_fd(fd) }),
                        )),
                        _ => Err(CreateRulesetError::CreateRulesetCall {
                            source: Error::last_os_error(),
                        }),
                    }
                }
            }
        };
        Ok(body()?)
    }
}

impl OptionCompatLevelMut for Ruleset {
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
        &mut self.compat.level
    }
}

impl OptionCompatLevelMut for &mut Ruleset {
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
        &mut self.compat.level
    }
}

impl Compatible for Ruleset {}

impl Compatible for &mut Ruleset {}

impl AsMut<Ruleset> for Ruleset {
    fn as_mut(&mut self) -> &mut Ruleset {
        self
    }
}

// Tests unambiguous type.
#[test]
fn ruleset_as_mut() {
    let mut ruleset = Ruleset::from(ABI::Unsupported);
    let _ = ruleset.as_mut();

    let mut ruleset_created = Ruleset::from(ABI::Unsupported)
        .handle_access(AccessFs::Execute)
        .unwrap()
        .create()
        .unwrap();
    let _ = ruleset_created.as_mut();
}

pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
    /// Attempts to add a set of access rights that will be supported by this ruleset.
    /// By default, all actions requiring these access rights will be denied.
    /// Consecutive calls to `handle_access()` will be interpreted as logical ORs
    /// with the previous handled accesses.
    ///
    /// On error, returns a wrapped [`HandleAccessesError`](crate::HandleAccessesError).
    /// E.g., `RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError<AccessFs>))`
    fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
    where
        T: Into<BitFlags<U>>,
        U: HandledAccess + PrivateHandledAccess,
    {
        U::ruleset_handle_access(self.as_mut(), access.into())?;
        Ok(self)
    }

    /// Attempts to add a set of scopes that will be supported by this ruleset.
    /// Consecutive calls to `scope()` will be interpreted as logical ORs
    /// with the previous scopes.
    ///
    /// On error, returns a wrapped [`ScopeError`](crate::ScopeError).
    /// E.g., `RulesetError::Scope(ScopeError)`
    fn scope<T>(mut self, scope: T) -> Result<Self, RulesetError>
    where
        T: Into<BitFlags<Scope>>,
    {
        let scope = scope.into();
        let ruleset = self.as_mut();
        ruleset.requested_scoped |= scope;
        if let Some(a) = scope
            .try_compat(
                ruleset.compat.abi(),
                ruleset.compat.level,
                &mut ruleset.compat.state,
            )
            .map_err(ScopeError::Compat)?
        {
            ruleset.actual_scoped |= a;
        }
        Ok(self)
    }
}

impl RulesetAttr for Ruleset {}

impl RulesetAttr for &mut Ruleset {}

#[test]
fn ruleset_attr() {
    let mut ruleset = Ruleset::from(ABI::Unsupported);
    let ruleset_ref = &mut ruleset;

    // Can pass this reference to prepare the ruleset...
    ruleset_ref
        .set_compatibility(CompatLevel::BestEffort)
        .handle_access(AccessFs::Execute)
        .unwrap()
        .handle_access(AccessFs::ReadFile)
        .unwrap();

    // ...and finally create the ruleset (thanks to non-lexical lifetimes).
    ruleset
        .set_compatibility(CompatLevel::BestEffort)
        .handle_access(AccessFs::Execute)
        .unwrap()
        .handle_access(AccessFs::WriteFile)
        .unwrap()
        .create()
        .unwrap();
}

#[test]
fn ruleset_created_handle_access_fs() {
    let access = make_bitflags!(AccessFs::{Execute | ReadDir});

    // Tests AccessFs::ruleset_handle_access()
    let ruleset = Ruleset::from(ABI::V1).handle_access(access).unwrap();
    assert_eq!(ruleset.requested_handled_fs, access);
    assert_eq!(ruleset.actual_handled_fs, access);

    // Tests composition (binary OR) of handled accesses.
    let ruleset = Ruleset::from(ABI::V1)
        .handle_access(AccessFs::Execute)
        .unwrap()
        .handle_access(AccessFs::ReadDir)
        .unwrap()
        .handle_access(AccessFs::Execute)
        .unwrap();
    assert_eq!(ruleset.requested_handled_fs, access);
    assert_eq!(ruleset.actual_handled_fs, access);

    // Tests that only the required handled accesses are reported as incompatible:
    // access should not contains AccessFs::Execute.
    assert!(matches!(Ruleset::from(ABI::Unsupported)
        .handle_access(AccessFs::Execute)
        .unwrap()
        .set_compatibility(CompatLevel::HardRequirement)
        .handle_access(AccessFs::ReadDir)
        .unwrap_err(),
        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
            CompatError::Access(AccessError::Incompatible { access })
        ))) if access == AccessFs::ReadDir
    ));
}

#[test]
fn ruleset_created_handle_access_net_tcp() {
    let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});

    // Tests AccessNet::ruleset_handle_access() with ABI that doesn't support TCP rights.
    let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();
    assert_eq!(ruleset.requested_handled_net, access);
    assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);

    // Tests AccessNet::ruleset_handle_access() with ABI that supports TCP rights.
    let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
    assert_eq!(ruleset.requested_handled_net, access);
    assert_eq!(ruleset.actual_handled_net, access);

    // Tests composition (binary OR) of handled accesses.
    let ruleset = Ruleset::from(ABI::V4)
        .handle_access(AccessNet::BindTcp)
        .unwrap()
        .handle_access(AccessNet::ConnectTcp)
        .unwrap()
        .handle_access(AccessNet::BindTcp)
        .unwrap();
    assert_eq!(ruleset.requested_handled_net, access);
    assert_eq!(ruleset.actual_handled_net, access);

    // Tests that only the required handled accesses are reported as incompatible:
    // access should not contains AccessNet::BindTcp.
    assert!(matches!(Ruleset::from(ABI::Unsupported)
        .handle_access(AccessNet::BindTcp)
        .unwrap()
        .set_compatibility(CompatLevel::HardRequirement)
        .handle_access(AccessNet::ConnectTcp)
        .unwrap_err(),
        RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
            CompatError::Access(AccessError::Incompatible { access })
        ))) if access == AccessNet::ConnectTcp
    ));
}

#[test]
fn ruleset_created_scope() {
    let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});

    // Tests Ruleset::scope() with ABI that doesn't support scopes.
    let ruleset = Ruleset::from(ABI::V5).scope(scopes).unwrap();
    assert_eq!(ruleset.requested_scoped, scopes);
    assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);

    // Tests Ruleset::scope() with ABI that supports scopes.
    let ruleset = Ruleset::from(ABI::V6).scope(scopes).unwrap();
    assert_eq!(ruleset.requested_scoped, scopes);
    assert_eq!(ruleset.actual_scoped, scopes);

    // Tests composition (binary OR) of scopes.
    let ruleset = Ruleset::from(ABI::V6)
        .scope(Scope::AbstractUnixSocket)
        .unwrap()
        .scope(Scope::Signal)
        .unwrap()
        .scope(Scope::AbstractUnixSocket)
        .unwrap();
    assert_eq!(ruleset.requested_scoped, scopes);
    assert_eq!(ruleset.actual_scoped, scopes);

    // Tests that only the required scopes are reported as incompatible:
    // scope should not contain Scope::AbstractUnixSocket.
    assert!(matches!(Ruleset::from(ABI::Unsupported)
        .scope(Scope::AbstractUnixSocket)
        .unwrap()
        .set_compatibility(CompatLevel::HardRequirement)
        .scope(Scope::Signal)
        .unwrap_err(),
        RulesetError::Scope(ScopeError::Compat(
            CompatError::Access(AccessError::Incompatible { access })
        )) if access == Scope::Signal
    ));
}

#[test]
fn ruleset_created_fs_net_scope() {
    let access_fs = make_bitflags!(AccessFs::{Execute | ReadDir});
    let access_net = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
    let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});

    // Tests composition (binary OR) of handled accesses.
    let ruleset = Ruleset::from(ABI::V5)
        .handle_access(access_fs)
        .unwrap()
        .scope(scopes)
        .unwrap()
        .handle_access(access_net)
        .unwrap();
    assert_eq!(ruleset.requested_handled_fs, access_fs);
    assert_eq!(ruleset.actual_handled_fs, access_fs);
    assert_eq!(ruleset.requested_handled_net, access_net);
    assert_eq!(ruleset.actual_handled_net, access_net);
    assert_eq!(ruleset.requested_scoped, scopes);
    assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);

    // Tests composition (binary OR) of handled accesses and scopes.
    let ruleset = Ruleset::from(ABI::V6)
        .handle_access(access_fs)
        .unwrap()
        .scope(scopes)
        .unwrap()
        .handle_access(access_net)
        .unwrap();
    assert_eq!(ruleset.requested_handled_fs, access_fs);
    assert_eq!(ruleset.actual_handled_fs, access_fs);
    assert_eq!(ruleset.requested_handled_net, access_net);
    assert_eq!(ruleset.actual_handled_net, access_net);
    assert_eq!(ruleset.requested_scoped, scopes);
    assert_eq!(ruleset.actual_scoped, scopes);
}

impl OptionCompatLevelMut for RulesetCreated {
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
        &mut self.compat.level
    }
}

impl OptionCompatLevelMut for &mut RulesetCreated {
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
        &mut self.compat.level
    }
}

impl Compatible for RulesetCreated {}

impl Compatible for &mut RulesetCreated {}

pub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {
    /// Attempts to add a new rule to the ruleset.
    ///
    /// On error, returns a wrapped [`AddRulesError`].
    fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
    where
        T: Rule<U>,
        U: HandledAccess + PrivateHandledAccess,
    {
        let body = || -> Result<Self, AddRulesError> {
            let self_ref = self.as_mut();
            rule.check_consistency(self_ref)?;
            let mut compat_rule = match rule
                .try_compat(
                    self_ref.compat.abi(),
                    self_ref.compat.level,
                    &mut self_ref.compat.state,
                )
                .map_err(AddRuleError::Compat)?
            {
                Some(r) => r,
                None => return Ok(self),
            };
            match self_ref.compat.state {
                CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
                CompatState::Full | CompatState::Partial => {
                    #[cfg(test)]
                    assert!(self_ref.fd.is_some());
                    let fd = self_ref.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
                    match unsafe {
                        uapi::landlock_add_rule(fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
                    } {
                        0 => Ok(self),
                        _ => Err(AddRuleError::<U>::AddRuleCall {
                            source: Error::last_os_error(),
                        }
                        .into()),
                    }
                }
            }
        };
        Ok(body()?)
    }

    /// Attempts to add a set of new rules to the ruleset.
    ///
    /// On error, returns a (double) wrapped [`AddRulesError`].
    ///
    /// # Example
    ///
    /// Create a custom iterator to read paths from environment variable.
    ///
    /// ```
    /// use landlock::{
    ///     Access, AccessFs, BitFlags, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
    ///     RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
    /// };
    /// use std::env;
    /// use std::ffi::OsStr;
    /// use std::os::unix::ffi::{OsStrExt, OsStringExt};
    /// use thiserror::Error;
    ///
    /// #[derive(Debug, Error)]
    /// enum PathEnvError<'a> {
    ///     #[error(transparent)]
    ///     Ruleset(#[from] RulesetError),
    ///     #[error(transparent)]
    ///     AddRuleIter(#[from] PathFdError),
    ///     #[error("missing environment variable {0}")]
    ///     MissingVar(&'a str),
    /// }
    ///
    /// struct PathEnv {
    ///     paths: Vec<u8>,
    ///     access: BitFlags<AccessFs>,
    /// }
    ///
    /// impl PathEnv {
    ///     // env_var is the name of an environment variable
    ///     // containing paths requested to be allowed.
    ///     // Paths are separated with ":", e.g. "/bin:/lib:/usr:/proc".
    ///     // In case an empty string is provided,
    ///     // no restrictions are applied.
    ///     // `access` is the set of access rights allowed for each of the parsed paths.
    ///     fn new<'a>(
    ///         env_var: &'a str, access: BitFlags<AccessFs>
    ///     ) -> Result<Self, PathEnvError<'a>> {
    ///         Ok(Self {
    ///             paths: env::var_os(env_var)
    ///                 .ok_or(PathEnvError::MissingVar(env_var))?
    ///                 .into_vec(),
    ///             access,
    ///         })
    ///     }
    ///
    ///     fn iter(
    ///         &self,
    ///     ) -> impl Iterator<Item = Result<PathBeneath<PathFd>, PathEnvError<'static>>> + '_ {
    ///         let is_empty = self.paths.is_empty();
    ///         self.paths
    ///             .split(|b| *b == b':')
    ///             // Skips the first empty element from of an empty string.
    ///             .skip_while(move |_| is_empty)
    ///             .map(OsStr::from_bytes)
    ///             .map(move |path|
    ///                 Ok(PathBeneath::new(PathFd::new(path)?, self.access)))
    ///     }
    /// }
    ///
    /// fn restrict_env() -> Result<RestrictionStatus, PathEnvError<'static>> {
    ///     Ok(Ruleset::default()
    ///         .handle_access(AccessFs::from_all(ABI::V1))?
    ///         .create()?
    ///         // In the shell: export EXECUTABLE_PATH="/usr:/bin:/sbin"
    ///         .add_rules(PathEnv::new("EXECUTABLE_PATH", AccessFs::Execute.into())?.iter())?
    ///         .restrict_self()?)
    /// }
    /// ```
    fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
    where
        I: IntoIterator<Item = Result<T, E>>,
        T: Rule<U>,
        U: HandledAccess + PrivateHandledAccess,
        E: From<RulesetError>,
    {
        for rule in rules {
            self = self.add_rule(rule?)?;
        }
        Ok(self)
    }

    /// Configures the ruleset to call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS` command
    /// in [`restrict_self()`](RulesetCreated::restrict_self).
    ///
    /// This `prctl(2)` call is never ignored, even if an error was encountered on a [`Ruleset`] or
    /// [`RulesetCreated`] method call while [`CompatLevel::SoftRequirement`] was set.
    fn set_no_new_privs(mut self, no_new_privs: bool) -> Self {
        <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = no_new_privs;
        self
    }
}

/// Ruleset created with [`Ruleset::create()`].
#[derive(Debug)]
pub struct RulesetCreated {
    fd: Option<OwnedFd>,
    no_new_privs: bool,
    pub(crate) requested_handled_fs: BitFlags<AccessFs>,
    pub(crate) requested_handled_net: BitFlags<AccessNet>,
    compat: Compatibility,
}

impl RulesetCreated {
    pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
        // The compatibility state is initialized by Ruleset::create().
        #[cfg(test)]
        assert!(!matches!(ruleset.compat.state, CompatState::Init));

        RulesetCreated {
            fd,
            no_new_privs: true,
            requested_handled_fs: ruleset.requested_handled_fs,
            requested_handled_net: ruleset.requested_handled_net,
            compat: ruleset.compat,
        }
    }

    /// Attempts to restrict the calling thread with the ruleset
    /// according to the best-effort configuration
    /// (see [`RulesetCreated::set_compatibility()`] and [`CompatLevel::BestEffort`]).
    /// Call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS`
    /// according to the ruleset configuration.
    ///
    /// On error, returns a wrapped [`RestrictSelfError`].
    pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
        let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
            // Enforce no_new_privs even if something failed with SoftRequirement. The rationale is
            // that no_new_privs should not be an issue on its own if it is not explicitly
            // deactivated.
            let enforced_nnp = if self.no_new_privs {
                if let Err(e) = prctl_set_no_new_privs() {
                    match self.compat.level.into() {
                        CompatLevel::BestEffort => {}
                        CompatLevel::SoftRequirement => {
                            self.compat.update(CompatState::Dummy);
                        }
                        CompatLevel::HardRequirement => {
                            return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
                        }
                    }
                    // To get a consistent behavior, calls this prctl whether or not
                    // Landlock is supported by the running kernel.
                    let support_nnp = support_no_new_privs();
                    match self.compat.state {
                        // It should not be an error for kernel (older than 3.5) not supporting
                        // no_new_privs.
                        CompatState::Init | CompatState::No | CompatState::Dummy => {
                            if support_nnp {
                                // The kernel seems to be between 3.5 (included) and 5.13 (excluded),
                                // or Landlock is not enabled; no_new_privs should be supported anyway.
                                return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
                            }
                        }
                        // A kernel supporting Landlock should also support no_new_privs (unless
                        // filtered by seccomp).
                        CompatState::Full | CompatState::Partial => {
                            return Err(RestrictSelfError::SetNoNewPrivsCall { source: e })
                        }
                    }
                    false
                } else {
                    true
                }
            } else {
                false
            };

            match self.compat.state {
                CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
                    ruleset: self.compat.state.into(),
                    landlock: self.compat.status(),
                    no_new_privs: enforced_nnp,
                }),
                CompatState::Full | CompatState::Partial => {
                    #[cfg(test)]
                    assert!(self.fd.is_some());
                    // Does not consume ruleset FD, which will be automatically closed after this block.
                    let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
                    match unsafe { uapi::landlock_restrict_self(fd, 0) } {
                        0 => {
                            self.compat.update(CompatState::Full);
                            Ok(RestrictionStatus {
                                ruleset: self.compat.state.into(),
                                landlock: self.compat.status(),
                                no_new_privs: enforced_nnp,
                            })
                        }
                        // TODO: match specific Landlock restrict self errors
                        _ => Err(RestrictSelfError::RestrictSelfCall {
                            source: Error::last_os_error(),
                        }),
                    }
                }
            }
        };
        Ok(body()?)
    }

    /// Creates a new `RulesetCreated` instance by duplicating the underlying file descriptor.
    /// Rule modification will affect both `RulesetCreated` instances simultaneously.
    ///
    /// On error, returns [`std::io::Error`].
    pub fn try_clone(&self) -> std::io::Result<Self> {
        Ok(RulesetCreated {
            fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
            no_new_privs: self.no_new_privs,
            requested_handled_fs: self.requested_handled_fs,
            requested_handled_net: self.requested_handled_net,
            compat: self.compat,
        })
    }
}

impl From<RulesetCreated> for Option<OwnedFd> {
    fn from(ruleset: RulesetCreated) -> Self {
        ruleset.fd
    }
}

#[test]
fn ruleset_created_ownedfd_none() {
    let ruleset = Ruleset::from(ABI::Unsupported)
        .handle_access(AccessFs::Execute)
        .unwrap()
        .create()
        .unwrap();
    let fd: Option<OwnedFd> = ruleset.into();
    assert!(fd.is_none());
}

impl AsMut<RulesetCreated> for RulesetCreated {
    fn as_mut(&mut self) -> &mut RulesetCreated {
        self
    }
}

impl RulesetCreatedAttr for RulesetCreated {}

impl RulesetCreatedAttr for &mut RulesetCreated {}

#[test]
fn ruleset_created_attr() {
    let mut ruleset_created = Ruleset::from(ABI::Unsupported)
        .handle_access(AccessFs::Execute)
        .unwrap()
        .create()
        .unwrap();
    let ruleset_created_ref = &mut ruleset_created;

    // Can pass this reference to populate the ruleset...
    ruleset_created_ref
        .set_compatibility(CompatLevel::BestEffort)
        .add_rule(PathBeneath::new(
            PathFd::new("/usr").unwrap(),
            AccessFs::Execute,
        ))
        .unwrap()
        .add_rule(PathBeneath::new(
            PathFd::new("/etc").unwrap(),
            AccessFs::Execute,
        ))
        .unwrap();

    // ...and finally restrict with the last rules (thanks to non-lexical lifetimes).
    assert_eq!(
        ruleset_created
            .set_compatibility(CompatLevel::BestEffort)
            .add_rule(PathBeneath::new(
                PathFd::new("/tmp").unwrap(),
                AccessFs::Execute,
            ))
            .unwrap()
            .add_rule(PathBeneath::new(
                PathFd::new("/var").unwrap(),
                AccessFs::Execute,
            ))
            .unwrap()
            .restrict_self()
            .unwrap(),
        RestrictionStatus {
            ruleset: RulesetStatus::NotEnforced,
            landlock: LandlockStatus::NotImplemented,
            no_new_privs: true,
        }
    );
}

#[test]
fn ruleset_compat_dummy() {
    for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
        println!("level: {:?}", level);

        // ABI:Unsupported does not support AccessFs::Execute.
        let ruleset = Ruleset::from(ABI::Unsupported);
        assert_eq!(ruleset.compat.state, CompatState::Init);

        let ruleset = ruleset.set_compatibility(level);
        assert_eq!(ruleset.compat.state, CompatState::Init);

        let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
        assert_eq!(
            ruleset.compat.state,
            match level {
                CompatLevel::BestEffort => CompatState::No,
                CompatLevel::SoftRequirement => CompatState::Dummy,
                _ => unreachable!(),
            }
        );

        let ruleset_created = ruleset.create().unwrap();
        // Because the compatibility state was either No or Dummy, calling create() updates it to
        // Dummy.
        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);

        let ruleset_created = ruleset_created
            .add_rule(PathBeneath::new(
                PathFd::new("/usr").unwrap(),
                AccessFs::Execute,
            ))
            .unwrap();
        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
    }
}

#[test]
fn ruleset_compat_partial() {
    // CompatLevel::BestEffort
    let ruleset = Ruleset::from(ABI::V1);
    assert_eq!(ruleset.compat.state, CompatState::Init);

    // ABI::V1 does not support AccessFs::Refer.
    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
    assert_eq!(ruleset.compat.state, CompatState::No);

    let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
    assert_eq!(ruleset.compat.state, CompatState::Partial);

    // Requesting to handle another unsupported handled access does not change anything.
    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
    assert_eq!(ruleset.compat.state, CompatState::Partial);
}

#[test]
fn ruleset_unsupported() {
    assert_eq!(
        Ruleset::from(ABI::Unsupported)
            // BestEffort for Ruleset.
            .handle_access(AccessFs::Execute)
            .unwrap()
            .create()
            .unwrap()
            .restrict_self()
            .unwrap(),
        RestrictionStatus {
            ruleset: RulesetStatus::NotEnforced,
            landlock: LandlockStatus::NotImplemented,
            // With BestEffort, no_new_privs is still enabled.
            no_new_privs: true,
        }
    );

    assert_eq!(
        Ruleset::from(ABI::Unsupported)
            // SoftRequirement for Ruleset.
            .set_compatibility(CompatLevel::SoftRequirement)
            .handle_access(AccessFs::Execute)
            .unwrap()
            .create()
            .unwrap()
            .restrict_self()
            .unwrap(),
        RestrictionStatus {
            ruleset: RulesetStatus::NotEnforced,
            landlock: LandlockStatus::NotImplemented,
            // With SoftRequirement, no_new_privs is still enabled.
            no_new_privs: true,
        }
    );

    // Missing handled access because of the compatibility level.
    matches!(
        Ruleset::from(ABI::Unsupported)
            // HardRequirement for Ruleset.
            .set_compatibility(CompatLevel::HardRequirement)
            .handle_access(AccessFs::Execute)
            .unwrap_err(),
        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
    );

    // Missing scope access because of the compatibility level.
    matches!(
        Ruleset::from(ABI::Unsupported)
            // HardRequirement for Ruleset.
            .set_compatibility(CompatLevel::HardRequirement)
            .scope(Scope::Signal)
            .unwrap_err(),
        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
    );

    assert_eq!(
        Ruleset::from(ABI::Unsupported)
            .handle_access(AccessFs::Execute)
            .unwrap()
            .create()
            .unwrap()
            // SoftRequirement for RulesetCreated without any rule.
            .set_compatibility(CompatLevel::SoftRequirement)
            .restrict_self()
            .unwrap(),
        RestrictionStatus {
            ruleset: RulesetStatus::NotEnforced,
            landlock: LandlockStatus::NotImplemented,
            // With SoftRequirement, no_new_privs is untouched if there is no error (e.g. no rule).
            no_new_privs: true,
        }
    );

    // Don't explicitly call create() on a CI that doesn't support Landlock.
    if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
        assert_eq!(
            Ruleset::from(ABI::V1)
                .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
                .unwrap()
                .create()
                .unwrap()
                // SoftRequirement for RulesetCreated with a rule.
                .set_compatibility(CompatLevel::SoftRequirement)
                .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
                .unwrap()
                .restrict_self()
                .unwrap(),
            RestrictionStatus {
                ruleset: RulesetStatus::NotEnforced,
                landlock: LandlockStatus::Available {
                    effective_abi: ABI::V1,
                    kernel_abi: None,
                },
                // With SoftRequirement, no_new_privs is still enabled, even if there is an error
                // (e.g. unsupported access right).
                no_new_privs: true,
            }
        );
    }

    assert_eq!(
        Ruleset::from(ABI::Unsupported)
            .handle_access(AccessFs::Execute)
            .unwrap()
            .create()
            .unwrap()
            .set_no_new_privs(false)
            .restrict_self()
            .unwrap(),
        RestrictionStatus {
            ruleset: RulesetStatus::NotEnforced,
            landlock: LandlockStatus::NotImplemented,
            no_new_privs: false,
        }
    );

    // Checks empty handled access with moot ruleset.
    assert!(matches!(
        Ruleset::from(ABI::Unsupported)
            // Empty access-rights
            .handle_access(AccessFs::from_all(ABI::Unsupported))
            .unwrap_err(),
        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
            CompatError::Access(AccessError::Empty)
        )))
    ));

    assert!(matches!(
        Ruleset::from(ABI::Unsupported)
            // No handle_access() nor scope() call.
            .create()
            .unwrap_err(),
        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
    ));

    // Checks empty handled access with minimal ruleset.
    assert!(matches!(
        Ruleset::from(ABI::V1)
            // Empty access-rights
            .handle_access(AccessFs::from_all(ABI::Unsupported))
            .unwrap_err(),
        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
            CompatError::Access(AccessError::Empty)
        )))
    ));

    // Checks empty scope with moot ruleset.
    assert!(matches!(
        Ruleset::from(ABI::Unsupported)
            .scope(Scope::from_all(ABI::Unsupported))
            .unwrap_err(),
        RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
    ));

    // Checks empty scope with minimal ruleset.
    assert!(matches!(
        Ruleset::from(ABI::V1)
            .scope(Scope::from_all(ABI::Unsupported))
            .unwrap_err(),
        RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
    ));

    // Scope with SoftRequirement on unsupported ABI: silently dropped, state becomes Dummy.
    let ruleset = Ruleset::from(ABI::V1)
        .handle_access(AccessFs::Execute)
        .unwrap()
        .set_compatibility(CompatLevel::SoftRequirement)
        .scope(Scope::Signal)
        .unwrap();
    assert_eq!(ruleset.requested_scoped, BitFlags::from(Scope::Signal));
    assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);

    // Tests inconsistency between the ruleset handled access-rights and the rule access-rights.
    for handled_access in &[
        make_bitflags!(AccessFs::{Execute | WriteFile}),
        AccessFs::Execute.into(),
    ] {
        let ruleset = Ruleset::from(ABI::V1)
            .handle_access(*handled_access)
            .unwrap();
        // Fakes a call to create() to test without involving the kernel (i.e. no
        // landlock_ruleset_create() call).
        let ruleset_created = RulesetCreated::new(ruleset, None);
        assert!(matches!(
            ruleset_created
                .add_rule(PathBeneath::new(
                    PathFd::new("/").unwrap(),
                    AccessFs::ReadFile
                ))
                .unwrap_err(),
            RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
        ));
    }
}

#[test]
fn ignore_abi_v2_with_abi_v1() {
    // We don't need kernel/CI support for Landlock because no related syscalls should actually be
    // performed.
    assert_eq!(
        Ruleset::from(ABI::V1)
            .set_compatibility(CompatLevel::HardRequirement)
            .handle_access(AccessFs::from_all(ABI::V1))
            .unwrap()
            .set_compatibility(CompatLevel::SoftRequirement)
            // Because Ruleset only supports V1, Refer will be ignored.
            .handle_access(AccessFs::Refer)
            .unwrap()
            .create()
            .unwrap()
            .add_rule(PathBeneath::new(
                PathFd::new("/tmp").unwrap(),
                AccessFs::from_all(ABI::V2)
            ))
            .unwrap()
            .add_rule(PathBeneath::new(
                PathFd::new("/usr").unwrap(),
                make_bitflags!(AccessFs::{ReadFile | ReadDir})
            ))
            .unwrap()
            .restrict_self()
            .unwrap(),
        RestrictionStatus {
            ruleset: RulesetStatus::NotEnforced,
            landlock: LandlockStatus::Available {
                effective_abi: ABI::V1,
                kernel_abi: None,
            },
            no_new_privs: true,
        }
    );
}

#[test]
fn unsupported_handled_access() {
    matches!(
        Ruleset::from(ABI::V3)
            .handle_access(AccessNet::from_all(ABI::V3))
            .unwrap_err(),
        RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
            CompatError::Access(AccessError::Empty)
        )))
    );
}

#[test]
fn unsupported_handled_access_errno() {
    assert_eq!(
        Errno::from(
            Ruleset::from(ABI::V3)
                .handle_access(AccessNet::from_all(ABI::V3))
                .unwrap_err()
        ),
        Errno::new(libc::EINVAL)
    );
}


================================================
FILE: src/scope.rs
================================================
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::{uapi, Access, ABI};
use enumflags2::{bitflags, BitFlags};

/// Scope right.
///
/// Each variant of `Scope` is a
/// [scope flag](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#scope-flags).
/// A set of scopes can be created with [`BitFlags<Scope>`](BitFlags).
///
/// # Example
///
/// ```
/// use landlock::{ABI, Access, Scope, BitFlags, make_bitflags};
///
/// let signal = Scope::Signal;
///
/// let signal_set: BitFlags<Scope> = signal.into();
///
/// let signal_uds = make_bitflags!(Scope::{Signal | AbstractUnixSocket});
///
/// let scope_v6 = Scope::from_all(ABI::V6);
///
/// assert_eq!(signal_uds, scope_v6);
/// ```
///
/// # Warning
///
/// To avoid unknown restrictions **don't use `BitFlags::<Scope>::all()` nor `BitFlags::ALL`**,
/// but use a version you tested and vetted instead,
/// for instance [`Scope::from_all(ABI::V6)`](Access::from_all).
/// Direct use of **the [`BitFlags`] API is deprecated**.
/// See [`ABI`] for the rationale and help to test it.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Scope {
    /// Restrict from connecting to abstract UNIX sockets created outside the sandbox.
    AbstractUnixSocket = uapi::LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET as u64,
    /// Restrict from sending signals to processes outside the sandbox.
    Signal = uapi::LANDLOCK_SCOPE_SIGNAL as u64,
}

/// # Warning
///
/// If `ABI <= ABI::V5`, `Scope::from_all()` returns an empty `BitFlags<AccessScope>`, which
/// makes `Ruleset::handle_access(AccessScope::from_all(ABI::V5))` return an error.
impl Access for Scope {
    fn from_all(abi: ABI) -> BitFlags<Self> {
        match abi {
            ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 => BitFlags::EMPTY,
            ABI::V6 => Scope::AbstractUnixSocket | Scope::Signal,
        }
    }
}


================================================
FILE: src/uapi/bindgen.sh
================================================
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0 OR MIT

set -u -e -o pipefail

if [[ $# -ne 1 ]]; then
	echo "usage $(basename -- "${BASH_SOURCE[0]}") <kernel-source>" >&2
	exit 1
fi

HEADER="$(readlink -f -- "$1")/include/uapi/linux/landlock.h"

if [[ ! -f "${HEADER}" ]]; then
	echo "File not found: ${HEADER}" >&2
	exit 1
fi

cd "$(dirname "${BASH_SOURCE[0]}")"

MSRV="$(sed -n 's/^rust-version = "\(.*\)"/\1/p' ../../Cargo.toml)"

bindgen_landlock() {
	local arch="$1"
	local output="$2"
	shift 2

	bindgen \
		"$@" \
		--rust-target "${MSRV}" \
		--allowlist-type "landlock_.*" \
		--allowlist-var "LANDLOCK_.*" \
		--no-doc-comments \
		--no-derive-default \
		--output "${output}" \
		"${HEADER}" \
		-- \
		--target="${arch}-linux-gnu"
}

for ARCH in x86_64 i686; do
	echo "Generating bindings with tests for ${ARCH}."
	bindgen_landlock "${ARCH}" "landlock_${ARCH}.rs"
done

# The Landlock ABI is architecture-agnostic (except for std::os::raw and memory
# alignment).
echo "Generating bindings without tests."
bindgen_landlock x86_64 "landlock_all.rs" --no-layout-tests


================================================
FILE: src/uapi/landlock_all.rs
================================================
/* automatically generated by rust-bindgen 0.72.1 */

pub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;
pub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2;
pub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1;
pub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2;
pub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4;
pub const LANDLOCK_ACCESS_FS_EXECUTE: u32 = 1;
pub const LANDLOCK_ACCESS_FS_WRITE_FILE: u32 = 2;
pub const LANDLOCK_ACCESS_FS_READ_FILE: u32 = 4;
pub const LANDLOCK_ACCESS_FS_READ_DIR: u32 = 8;
pub const LANDLOCK_ACCESS_FS_REMOVE_DIR: u32 = 16;
pub const LANDLOCK_ACCESS_FS_REMOVE_FILE: u32 = 32;
pub const LANDLOCK_ACCESS_FS_MAKE_CHAR: u32 = 64;
pub const LANDLOCK_ACCESS_FS_MAKE_DIR: u32 = 128;
pub const LANDLOCK_ACCESS_FS_MAKE_REG: u32 = 256;
pub const LANDLOCK_ACCESS_FS_MAKE_SOCK: u32 = 512;
pub const LANDLOCK_ACCESS_FS_MAKE_FIFO: u32 = 1024;
pub const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u32 = 2048;
pub const LANDLOCK_ACCESS_FS_MAKE_SYM: u32 = 4096;
pub const LANDLOCK_ACCESS_FS_REFER: u32 = 8192;
pub const LANDLOCK_ACCESS_FS_TRUNCATE: u32 = 16384;
pub const LANDLOCK_ACCESS_FS_IOCTL_DEV: u32 = 32768;
pub const LANDLOCK_ACCESS_NET_BIND_TCP: u32 = 1;
pub const LANDLOCK_ACCESS_NET_CONNECT_TCP: u32 = 2;
pub const LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: u32 = 1;
pub const LANDLOCK_SCOPE_SIGNAL: u32 = 2;
pub type __s32 = ::std::os::raw::c_int;
pub type __u64 = ::std::os::raw::c_ulonglong;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct landlock_ruleset_attr {
    pub handled_access_fs: __u64,
    pub handled_access_net: __u64,
    pub scoped: __u64,
}
pub const landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH: landlock_rule_type = 1;
pub const landlock_rule_type_LANDLOCK_RULE_NET_PORT: landlock_rule_type = 2;
pub type landlock_rule_type = ::std::os::raw::c_uint;
#[repr(C, packed)]
#[derive(Debug, Copy, Clone)]
pub struct landlock_path_beneath_attr {
    pub allowed_access: __u64,
    pub parent_fd: __s32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct landlock_net_port_attr {
    pub allowed_access: __u64,
    pub port: __u64,
}


================================================
FILE: src/uapi/landlock_i686.rs
================================================
/* automatically generated by rust-bindgen 0.72.1 */

pub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;
pub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2;
pub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1;
pub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2;
pub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4;
pub const LANDLOCK_ACCESS_FS_EXECUTE: u32 = 1;
pub const LANDLOCK_ACCESS_FS_WRITE_FILE: u32 = 2;
pub const LANDLOCK_ACCESS_FS_READ_FILE: u32 = 4;
pub const LANDLOCK_ACCESS_FS_READ_DIR: u32 = 8;
pub const LANDLOCK_ACCESS_FS_REMOVE_DIR: u32 = 16;
pub const LANDLOCK_ACCESS_FS_REMOVE_FILE: u32 = 32;
pub const LANDLOCK_ACCESS_FS_MAKE_CHAR: u32 = 64;
pub const LANDLOCK_ACCESS_FS_MAKE_DIR: u32 = 128;
pub const LANDLOCK_ACCESS_FS_MAKE_REG: u32 = 256;
pub const LANDLOCK_ACCESS_FS_MAKE_SOCK: u32 = 512;
pub const LANDLOCK_ACCESS_FS_MAKE_FIFO: u32 = 1024;
pub const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u32 = 2048;
pub const LANDLOCK_ACCESS_FS_MAKE_SYM: u32 = 4096;
pub const LANDLOCK_ACCESS_FS_REFER: u32 = 8192;
pub const LANDLOCK_ACCESS_FS_TRUNCATE: u32 = 16384;
pub const LANDLOCK_ACCESS_FS_IOCTL_DEV: u32 = 32768;
pub const LANDLOCK_ACCESS_NET_BIND_TCP: u32 = 1;
pub const LANDLOCK_ACCESS_NET_CONNECT_TCP: u32 = 2;
pub const LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: u32 = 1;
pub const LANDLOCK_SCOPE_SIGNAL: u32 = 2;
pub type __s32 = ::std::os::raw::c_int;
pub type __u64 = ::std::os::raw::c_ulonglong;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct landlock_ruleset_attr {
    pub handled_access_fs: __u64
Download .txt
gitextract_ih3s9dw9/

├── .github/
│   └── workflows/
│       ├── pages.yml
│       └── rust.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── COPYRIGHT
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── examples/
│   └── sandboxer.rs
└── src/
    ├── access.rs
    ├── compat.rs
    ├── errata.rs
    ├── errors.rs
    ├── fs.rs
    ├── lib.rs
    ├── net.rs
    ├── ruleset.rs
    ├── scope.rs
    └── uapi/
        ├── bindgen.sh
        ├── landlock_all.rs
        ├── landlock_i686.rs
        ├── landlock_x86_64.rs
        └── mod.rs
Download .txt
SYMBOL INDEX (314 symbols across 14 files)

FILE: examples/sandboxer.rs
  constant ENV_FS_RO_NAME (line 17) | const ENV_FS_RO_NAME: &str = "LL_FS_RO";
  constant ENV_FS_RW_NAME (line 18) | const ENV_FS_RW_NAME: &str = "LL_FS_RW";
  constant ENV_TCP_BIND_NAME (line 19) | const ENV_TCP_BIND_NAME: &str = "LL_TCP_BIND";
  constant ENV_TCP_CONNECT_NAME (line 20) | const ENV_TCP_CONNECT_NAME: &str = "LL_TCP_CONNECT";
  constant ENV_SCOPED_NAME (line 21) | const ENV_SCOPED_NAME: &str = "LL_SCOPED";
  type PathEnv (line 23) | struct PathEnv {
    method new (line 37) | fn new<'a>(name: &'a str, access: BitFlags<AccessFs>) -> anyhow::Resul...
    method iter (line 46) | fn iter(&self) -> impl Iterator<Item = anyhow::Result<PathBeneath<Path...
  type PortEnv (line 60) | struct PortEnv {
    method new (line 66) | fn new<'a>(name: &'a str, access: AccessNet) -> anyhow::Result<Self> {
    method iter (line 73) | fn iter(&self) -> impl Iterator<Item = anyhow::Result<NetPort>> + '_ {
  function main (line 91) | fn main() -> anyhow::Result<()> {

FILE: src/access.rs
  type Access (line 12) | pub trait Access: BitFlag + private::Sealed {
    method from_all (line 14) | fn from_all(abi: ABI) -> BitFlags<Self>;
  type HandledAccess (line 18) | pub trait HandledAccess: Access {}
  type PrivateHandledAccess (line 20) | pub trait PrivateHandledAccess: HandledAccess {
    method ruleset_handle_access (line 21) | fn ruleset_handle_access(
    method into_add_rules_error (line 28) | fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError
    method into_handle_accesses_error (line 32) | fn into_handle_accesses_error(error: HandleAccessError<Self>) -> Handl...
  function full_negation (line 38) | fn full_negation<T>(flags: BitFlags<T>) -> BitFlags<T>
  function bit_flags_full_negation (line 46) | fn bit_flags_full_negation() {
  function try_compat_inner (line 60) | fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, Comp...
  function compat_bit_flags (line 95) | fn compat_bit_flags() {

FILE: src/compat.rs
  type ABI (line 51) | pub enum ABI {
    method is_known (line 80) | fn is_known(value: i32) -> bool {
    method from (line 89) | fn from(value: i32) -> ABI {
    method from (line 236) | fn from(status: LandlockStatus) -> Self {
  function abi_from (line 104) | fn abi_from() {
  function known_abi (line 123) | fn known_abi() {
  method fmt (line 137) | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
  type LandlockStatus (line 156) | pub enum LandlockStatus {
    method current (line 187) | fn current() -> Self {
    method from (line 250) | fn from(abi: ABI) -> Self {
  function test_current_landlock_status (line 214) | fn test_current_landlock_status() {
  function can_emulate (line 282) | pub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support:...
  function get_errno_from_landlock_status (line 293) | pub(crate) fn get_errno_from_landlock_status() -> Option<i32> {
  function current_kernel_abi (line 313) | fn current_kernel_abi() {
  type CompatState (line 334) | pub enum CompatState {
    method update (line 348) | fn update(&mut self, other: Self) {
  function compat_state_update_1 (line 361) | fn compat_state_update_1() {
  function compat_state_update_2 (line 387) | fn compat_state_update_2() {
  type Compatibility (line 402) | pub(crate) struct Compatibility {
    method from (line 409) | fn from(status: LandlockStatus) -> Self {
    method from (line 420) | fn from(abi: ABI) -> Self {
    method new (line 428) | pub(crate) fn new() -> Self {
    method update (line 432) | pub(crate) fn update(&mut self, state: CompatState) {
    method abi (line 436) | pub(crate) fn abi(&self) -> ABI {
    method status (line 440) | pub(crate) fn status(&self) -> LandlockStatus {
  type OptionCompatLevelMut (line 448) | pub trait OptionCompatLevelMut {
    method as_option_compat_level_mut (line 449) | fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel>;
  type Compatible (line 468) | pub trait Compatible: Sized + private::OptionCompatLevelMut {
    method set_compatibility (line 571) | fn set_compatibility(mut self, level: CompatLevel) -> Self {
    method set_best_effort (line 582) | fn set_best_effort(self, best_effort: bool) -> Self
  function deprecated_set_best_effort (line 595) | fn deprecated_set_best_effort() {
  type CompatLevel (line 615) | pub enum CompatLevel {
    method from (line 635) | fn from(opt: Option<CompatLevel>) -> Self {
  type TailoredCompatLevel (line 646) | pub trait TailoredCompatLevel {
    method tailored_compat_level (line 647) | fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
    method tailored_compat_level (line 660) | fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
  function tailored_compat_level (line 676) | fn tailored_compat_level() {
  type CompatResult (line 712) | pub enum CompatResult<A>
  type TryCompat (line 725) | pub trait TryCompat<A>
    method try_compat_inner (line 730) | fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, Co...
    method try_compat_children (line 743) | fn try_compat_children<L>(
    method try_compat (line 757) | fn try_compat<L>(

FILE: src/errata.rs
  type Erratum (line 29) | pub enum Erratum {
    method current (line 61) | pub fn current() -> BitFlags<Self> {
  function from (line 90) | fn from(abi: ABI) -> Self {
  function not_backported_yet (line 110) | fn not_backported_yet(abi: ABI) -> BitFlags<Erratum> {
  function errata_query (line 123) | fn errata_query() {
  function errata_up_to_date (line 129) | fn errata_up_to_date() {

FILE: src/errors.rs
  type RulesetError (line 12) | pub enum RulesetError {
  function ruleset_error_breaking_change (line 26) | fn ruleset_error_breaking_change() {
  type HandleAccessError (line 38) | pub enum HandleAccessError<T>
  type ScopeError (line 49) | pub enum ScopeError {
  type HandleAccessesError (line 56) | pub enum HandleAccessesError {
    method from (line 69) | fn from(error: HandleAccessError<A>) -> Self {
  type CreateRulesetError (line 77) | pub enum CreateRulesetError {
  type AddRuleError (line 91) | pub enum AddRuleError<T>
  type AddRulesError (line 124) | pub enum AddRulesError {
    method from (line 115) | fn from(error: AddRuleError<A>) -> Self {
  type CompatError (line 133) | pub enum CompatError<T>
  type PathBeneathError (line 145) | pub enum PathBeneathError {
  type AccessError (line 166) | pub enum AccessError<T>
  type RestrictSelfError (line 196) | pub enum RestrictSelfError {
  type PathFdError (line 209) | pub enum PathFdError {
  type TestRulesetError (line 218) | pub(crate) enum TestRulesetError {
  type Errno (line 232) | pub struct Errno(c_int);
    method new (line 235) | pub fn new(value: c_int) -> Self {
    method from (line 244) | fn from(error: T) -> Self {
    type Target (line 256) | type Target = c_int;
    method deref (line 258) | fn deref(&self) -> &Self::Target {
  function _test_ruleset_errno (line 264) | fn _test_ruleset_errno(expected_errno: c_int) {
  function test_ruleset_errno (line 310) | fn test_ruleset_errno() {

FILE: src/fs.rs
  type AccessFs (line 58) | pub enum AccessFs {
    method from_read (line 112) | pub fn from_read(abi: ABI) -> BitFlags<Self> {
    method from_write (line 126) | pub fn from_write(abi: ABI) -> BitFlags<Self> {
    method from_file (line 148) | pub fn from_file(abi: ABI) -> BitFlags<Self> {
  method from_all (line 99) | fn from_all(abi: ABI) -> BitFlags<Self> {
  function consistent_access_fs_rw (line 154) | fn consistent_access_fs_rw() {
  method ruleset_handle_access (line 169) | fn ruleset_handle_access(
  method into_add_rules_error (line 189) | fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
  method into_handle_accesses_error (line 193) | fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleA...
  constant ACCESS_FILE (line 200) | const ACCESS_FILE: BitFlags<AccessFs> = make_bitflags!(AccessFs::{
  function is_file (line 205) | fn is_file<F>(fd: F) -> Result<bool, Error>
  type PathBeneath (line 230) | pub struct PathBeneath<F> {
  function new (line 245) | pub fn new<A>(parent: F, access: A) -> Self
  function try_compat_children (line 263) | fn try_compat_children<L>(
  function try_compat_inner (line 284) | fn try_compat_inner(
  function path_beneath_try_compat_children (line 312) | fn path_beneath_try_compat_children() {
  function path_beneath_try_compat (line 348) | fn path_beneath_try_compat() {
  method as_option_compat_level_mut (line 398) | fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
  method as_option_compat_level_mut (line 404) | fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
  function path_beneath_compatibility (line 414) | fn path_beneath_compatibility() {
  constant TYPE_ID (line 442) | const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDL...
  function as_ptr (line 444) | fn as_ptr(&mut self) -> *const libc::c_void {
  function check_consistency (line 450) | fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddR...
  function path_beneath_check_consistency (line 468) | fn path_beneath_check_consistency() {
  type PathFd (line 506) | pub struct PathFd {
    method new (line 511) | pub fn new<T>(path: T) -> Result<Self, PathFdError>
  method as_fd (line 531) | fn as_fd(&self) -> BorrowedFd<'_> {
  function path_fd (line 537) | fn path_fd() {
  function path_beneath_rules (line 594) | pub fn path_beneath_rules<I, P, A>(
  function path_beneath_rules_iter (line 618) | fn path_beneath_rules_iter() {

FILE: src/lib.rs
  type Sealed (line 124) | pub trait Sealed {}
  function check_ruleset_support (line 136) | fn check_ruleset_support<F>(
  function allow_root_compat (line 209) | fn allow_root_compat() {
  function too_much_access_rights_for_a_file (line 227) | fn too_much_access_rights_for_a_file() {
  function path_beneath_rules_with_too_much_access_rights_for_a_file (line 268) | fn path_beneath_rules_with_too_much_access_rights_for_a_file() {
  function allow_root_fragile (line 287) | fn allow_root_fragile() {
  function ruleset_enforced (line 312) | fn ruleset_enforced() {
  function abi_v2_exec_refer (line 330) | fn abi_v2_exec_refer() {
  function abi_v2_refer_only (line 347) | fn abi_v2_refer_only() {
  function abi_v3_truncate (line 363) | fn abi_v3_truncate() {
  function ruleset_created_try_clone (line 380) | fn ruleset_created_try_clone() {
  function abi_v4_tcp (line 397) | fn abi_v4_tcp() {
  function abi_v5_ioctl_dev (line 414) | fn abi_v5_ioctl_dev() {
  function abi_v6_scope_mix (line 431) | fn abi_v6_scope_mix() {
  function abi_v6_scope_only (line 447) | fn abi_v6_scope_only() {
  function ruleset_created_try_clone_ownedfd (line 462) | fn ruleset_created_try_clone_ownedfd() {

FILE: src/net.rs
  type AccessNet (line 45) | pub enum AccessNet {
  method from_all (line 57) | fn from_all(abi: ABI) -> BitFlags<Self> {
  method ruleset_handle_access (line 68) | fn ruleset_handle_access(
  method into_add_rules_error (line 88) | fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
  method into_handle_accesses_error (line 92) | fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleA...
  type NetPort (line 109) | pub struct NetPort {
    method new (line 124) | pub fn new<A>(port: u16, access: A) -> Self
    constant TYPE_ID (line 141) | const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LAN...
    method as_ptr (line 143) | fn as_ptr(&mut self) -> *const libc::c_void {
    method check_consistency (line 149) | fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), Ad...
    method try_compat_children (line 187) | fn try_compat_children<L>(
    method try_compat_inner (line 208) | fn try_compat_inner(
  function net_port_check_consistency (line 167) | fn net_port_check_consistency() {
  method as_option_compat_level_mut (line 217) | fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
  method as_option_compat_level_mut (line 223) | fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {

FILE: src/ruleset.rs
  type Rule (line 17) | pub trait Rule<T>: PrivateRule<T>
  type PrivateRule (line 24) | pub trait PrivateRule<T>
    constant TYPE_ID (line 29) | const TYPE_ID: uapi::landlock_rule_type;
    method as_ptr (line 35) | fn as_ptr(&mut self) -> *const libc::c_void;
    method check_consistency (line 37) | fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), Ad...
  type RulesetStatus (line 42) | pub enum RulesetStatus {
    method from (line 54) | fn from(state: CompatState) -> Self {
  type RestrictionStatus (line 69) | pub struct RestrictionStatus {
  function prctl_set_no_new_privs (line 78) | fn prctl_set_no_new_privs() -> Result<(), Error> {
  function support_no_new_privs (line 85) | fn support_no_new_privs() -> bool {
  type Ruleset (line 176) | pub struct Ruleset {
    method from (line 187) | fn from(compat: Compatibility) -> Self {
    method from (line 203) | fn from(abi: ABI) -> Self {
    method new (line 244) | pub fn new() -> Self {
    method create (line 252) | pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
    method as_mut (line 326) | fn as_mut(&mut self) -> &mut Ruleset {
  function ruleset_add_rule_iter (line 209) | fn ruleset_add_rule_iter() {
  method default (line 232) | fn default() -> Self {
  method as_option_compat_level_mut (line 310) | fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
  method as_option_compat_level_mut (line 316) | fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
  function ruleset_as_mut (line 333) | fn ruleset_as_mut() {
  type RulesetAttr (line 345) | pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
    method handle_access (line 353) | fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
    method scope (line 368) | fn scope<T>(mut self, scope: T) -> Result<Self, RulesetError>
  function ruleset_attr (line 394) | fn ruleset_attr() {
  function ruleset_created_handle_access_fs (line 418) | fn ruleset_created_handle_access_fs() {
  function ruleset_created_handle_access_net_tcp (line 452) | fn ruleset_created_handle_access_net_tcp() {
  function ruleset_created_scope (line 491) | fn ruleset_created_scope() {
  function ruleset_created_fs_net_scope (line 530) | fn ruleset_created_fs_net_scope() {
  method as_option_compat_level_mut (line 567) | fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
  method as_option_compat_level_mut (line 573) | fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
  type RulesetCreatedAttr (line 582) | pub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {
    method add_rule (line 586) | fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
    method add_rules (line 700) | fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
    method set_no_new_privs (line 718) | fn set_no_new_privs(mut self, no_new_privs: bool) -> Self {
  type RulesetCreated (line 726) | pub struct RulesetCreated {
    method new (line 735) | pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
    method restrict_self (line 756) | pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetErr...
    method try_clone (line 834) | pub fn try_clone(&self) -> std::io::Result<Self> {
    method as_mut (line 863) | fn as_mut(&mut self) -> &mut RulesetCreated {
  function from (line 846) | fn from(ruleset: RulesetCreated) -> Self {
  function ruleset_created_ownedfd_none (line 852) | fn ruleset_created_ownedfd_none() {
  function ruleset_created_attr (line 873) | fn ruleset_created_attr() {
  function ruleset_compat_dummy (line 920) | fn ruleset_compat_dummy() {
  function ruleset_compat_partial (line 957) | fn ruleset_compat_partial() {
  function ruleset_unsupported (line 975) | fn ruleset_unsupported() {
  function ignore_abi_v2_with_abi_v1 (line 1172) | fn ignore_abi_v2_with_abi_v1() {
  function unsupported_handled_access (line 1210) | fn unsupported_handled_access() {
  function unsupported_handled_access_errno (line 1222) | fn unsupported_handled_access_errno() {

FILE: src/scope.rs
  type Scope (line 39) | pub enum Scope {
  method from_all (line 51) | fn from_all(abi: ABI) -> BitFlags<Self> {

FILE: src/uapi/landlock_all.rs
  constant LANDLOCK_CREATE_RULESET_VERSION (line 3) | pub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;
  constant LANDLOCK_CREATE_RULESET_ERRATA (line 4) | pub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2;
  constant LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF (line 5) | pub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1;
  constant LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (line 6) | pub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2;
  constant LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF (line 7) | pub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4;
  constant LANDLOCK_ACCESS_FS_EXECUTE (line 8) | pub const LANDLOCK_ACCESS_FS_EXECUTE: u32 = 1;
  constant LANDLOCK_ACCESS_FS_WRITE_FILE (line 9) | pub const LANDLOCK_ACCESS_FS_WRITE_FILE: u32 = 2;
  constant LANDLOCK_ACCESS_FS_READ_FILE (line 10) | pub const LANDLOCK_ACCESS_FS_READ_FILE: u32 = 4;
  constant LANDLOCK_ACCESS_FS_READ_DIR (line 11) | pub const LANDLOCK_ACCESS_FS_READ_DIR: u32 = 8;
  constant LANDLOCK_ACCESS_FS_REMOVE_DIR (line 12) | pub const LANDLOCK_ACCESS_FS_REMOVE_DIR: u32 = 16;
  constant LANDLOCK_ACCESS_FS_REMOVE_FILE (line 13) | pub const LANDLOCK_ACCESS_FS_REMOVE_FILE: u32 = 32;
  constant LANDLOCK_ACCESS_FS_MAKE_CHAR (line 14) | pub const LANDLOCK_ACCESS_FS_MAKE_CHAR: u32 = 64;
  constant LANDLOCK_ACCESS_FS_MAKE_DIR (line 15) | pub const LANDLOCK_ACCESS_FS_MAKE_DIR: u32 = 128;
  constant LANDLOCK_ACCESS_FS_MAKE_REG (line 16) | pub const LANDLOCK_ACCESS_FS_MAKE_REG: u32 = 256;
  constant LANDLOCK_ACCESS_FS_MAKE_SOCK (line 17) | pub const LANDLOCK_ACCESS_FS_MAKE_SOCK: u32 = 512;
  constant LANDLOCK_ACCESS_FS_MAKE_FIFO (line 18) | pub const LANDLOCK_ACCESS_FS_MAKE_FIFO: u32 = 1024;
  constant LANDLOCK_ACCESS_FS_MAKE_BLOCK (line 19) | pub const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u32 = 2048;
  constant LANDLOCK_ACCESS_FS_MAKE_SYM (line 20) | pub const LANDLOCK_ACCESS_FS_MAKE_SYM: u32 = 4096;
  constant LANDLOCK_ACCESS_FS_REFER (line 21) | pub const LANDLOCK_ACCESS_FS_REFER: u32 = 8192;
  constant LANDLOCK_ACCESS_FS_TRUNCATE (line 22) | pub const LANDLOCK_ACCESS_FS_TRUNCATE: u32 = 16384;
  constant LANDLOCK_ACCESS_FS_IOCTL_DEV (line 23) | pub const LANDLOCK_ACCESS_FS_IOCTL_DEV: u32 = 32768;
  constant LANDLOCK_ACCESS_NET_BIND_TCP (line 24) | pub const LANDLOCK_ACCESS_NET_BIND_TCP: u32 = 1;
  constant LANDLOCK_ACCESS_NET_CONNECT_TCP (line 25) | pub const LANDLOCK_ACCESS_NET_CONNECT_TCP: u32 = 2;
  constant LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (line 26) | pub const LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: u32 = 1;
  constant LANDLOCK_SCOPE_SIGNAL (line 27) | pub const LANDLOCK_SCOPE_SIGNAL: u32 = 2;
  type __s32 (line 28) | pub type __s32 = ::std::os::raw::c_int;
  type __u64 (line 29) | pub type __u64 = ::std::os::raw::c_ulonglong;
  type landlock_ruleset_attr (line 32) | pub struct landlock_ruleset_attr {
  constant landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH (line 37) | pub const landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH: landlock_rule_t...
  constant landlock_rule_type_LANDLOCK_RULE_NET_PORT (line 38) | pub const landlock_rule_type_LANDLOCK_RULE_NET_PORT: landlock_rule_type ...
  type landlock_rule_type (line 39) | pub type landlock_rule_type = ::std::os::raw::c_uint;
  type landlock_path_beneath_attr (line 42) | pub struct landlock_path_beneath_attr {
  type landlock_net_port_attr (line 48) | pub struct landlock_net_port_attr {

FILE: src/uapi/landlock_i686.rs
  constant LANDLOCK_CREATE_RULESET_VERSION (line 3) | pub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;
  constant LANDLOCK_CREATE_RULESET_ERRATA (line 4) | pub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2;
  constant LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF (line 5) | pub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1;
  constant LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (line 6) | pub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2;
  constant LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF (line 7) | pub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4;
  constant LANDLOCK_ACCESS_FS_EXECUTE (line 8) | pub const LANDLOCK_ACCESS_FS_EXECUTE: u32 = 1;
  constant LANDLOCK_ACCESS_FS_WRITE_FILE (line 9) | pub const LANDLOCK_ACCESS_FS_WRITE_FILE: u32 = 2;
  constant LANDLOCK_ACCESS_FS_READ_FILE (line 10) | pub const LANDLOCK_ACCESS_FS_READ_FILE: u32 = 4;
  constant LANDLOCK_ACCESS_FS_READ_DIR (line 11) | pub const LANDLOCK_ACCESS_FS_READ_DIR: u32 = 8;
  constant LANDLOCK_ACCESS_FS_REMOVE_DIR (line 12) | pub const LANDLOCK_ACCESS_FS_REMOVE_DIR: u32 = 16;
  constant LANDLOCK_ACCESS_FS_REMOVE_FILE (line 13) | pub const LANDLOCK_ACCESS_FS_REMOVE_FILE: u32 = 32;
  constant LANDLOCK_ACCESS_FS_MAKE_CHAR (line 14) | pub const LANDLOCK_ACCESS_FS_MAKE_CHAR: u32 = 64;
  constant LANDLOCK_ACCESS_FS_MAKE_DIR (line 15) | pub const LANDLOCK_ACCESS_FS_MAKE_DIR: u32 = 128;
  constant LANDLOCK_ACCESS_FS_MAKE_REG (line 16) | pub const LANDLOCK_ACCESS_FS_MAKE_REG: u32 = 256;
  constant LANDLOCK_ACCESS_FS_MAKE_SOCK (line 17) | pub const LANDLOCK_ACCESS_FS_MAKE_SOCK: u32 = 512;
  constant LANDLOCK_ACCESS_FS_MAKE_FIFO (line 18) | pub const LANDLOCK_ACCESS_FS_MAKE_FIFO: u32 = 1024;
  constant LANDLOCK_ACCESS_FS_MAKE_BLOCK (line 19) | pub const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u32 = 2048;
  constant LANDLOCK_ACCESS_FS_MAKE_SYM (line 20) | pub const LANDLOCK_ACCESS_FS_MAKE_SYM: u32 = 4096;
  constant LANDLOCK_ACCESS_FS_REFER (line 21) | pub const LANDLOCK_ACCESS_FS_REFER: u32 = 8192;
  constant LANDLOCK_ACCESS_FS_TRUNCATE (line 22) | pub const LANDLOCK_ACCESS_FS_TRUNCATE: u32 = 16384;
  constant LANDLOCK_ACCESS_FS_IOCTL_DEV (line 23) | pub const LANDLOCK_ACCESS_FS_IOCTL_DEV: u32 = 32768;
  constant LANDLOCK_ACCESS_NET_BIND_TCP (line 24) | pub const LANDLOCK_ACCESS_NET_BIND_TCP: u32 = 1;
  constant LANDLOCK_ACCESS_NET_CONNECT_TCP (line 25) | pub const LANDLOCK_ACCESS_NET_CONNECT_TCP: u32 = 2;
  constant LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (line 26) | pub const LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: u32 = 1;
  constant LANDLOCK_SCOPE_SIGNAL (line 27) | pub const LANDLOCK_SCOPE_SIGNAL: u32 = 2;
  type __s32 (line 28) | pub type __s32 = ::std::os::raw::c_int;
  type __u64 (line 29) | pub type __u64 = ::std::os::raw::c_ulonglong;
  type landlock_ruleset_attr (line 32) | pub struct landlock_ruleset_attr {
  function bindgen_test_layout_landlock_ruleset_attr (line 38) | fn bindgen_test_layout_landlock_ruleset_attr() {
  constant landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH (line 68) | pub const landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH: landlock_rule_t...
  constant landlock_rule_type_LANDLOCK_RULE_NET_PORT (line 69) | pub const landlock_rule_type_LANDLOCK_RULE_NET_PORT: landlock_rule_type ...
  type landlock_rule_type (line 70) | pub type landlock_rule_type = ::std::os::raw::c_uint;
  type landlock_path_beneath_attr (line 73) | pub struct landlock_path_beneath_attr {
  function bindgen_test_layout_landlock_path_beneath_attr (line 78) | fn bindgen_test_layout_landlock_path_beneath_attr() {
  type landlock_net_port_attr (line 105) | pub struct landlock_net_port_attr {
  function bindgen_test_layout_landlock_net_port_attr (line 110) | fn bindgen_test_layout_landlock_net_port_attr() {

FILE: src/uapi/landlock_x86_64.rs
  constant LANDLOCK_CREATE_RULESET_VERSION (line 3) | pub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;
  constant LANDLOCK_CREATE_RULESET_ERRATA (line 4) | pub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2;
  constant LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF (line 5) | pub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1;
  constant LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (line 6) | pub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2;
  constant LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF (line 7) | pub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4;
  constant LANDLOCK_ACCESS_FS_EXECUTE (line 8) | pub const LANDLOCK_ACCESS_FS_EXECUTE: u32 = 1;
  constant LANDLOCK_ACCESS_FS_WRITE_FILE (line 9) | pub const LANDLOCK_ACCESS_FS_WRITE_FILE: u32 = 2;
  constant LANDLOCK_ACCESS_FS_READ_FILE (line 10) | pub const LANDLOCK_ACCESS_FS_READ_FILE: u32 = 4;
  constant LANDLOCK_ACCESS_FS_READ_DIR (line 11) | pub const LANDLOCK_ACCESS_FS_READ_DIR: u32 = 8;
  constant LANDLOCK_ACCESS_FS_REMOVE_DIR (line 12) | pub const LANDLOCK_ACCESS_FS_REMOVE_DIR: u32 = 16;
  constant LANDLOCK_ACCESS_FS_REMOVE_FILE (line 13) | pub const LANDLOCK_ACCESS_FS_REMOVE_FILE: u32 = 32;
  constant LANDLOCK_ACCESS_FS_MAKE_CHAR (line 14) | pub const LANDLOCK_ACCESS_FS_MAKE_CHAR: u32 = 64;
  constant LANDLOCK_ACCESS_FS_MAKE_DIR (line 15) | pub const LANDLOCK_ACCESS_FS_MAKE_DIR: u32 = 128;
  constant LANDLOCK_ACCESS_FS_MAKE_REG (line 16) | pub const LANDLOCK_ACCESS_FS_MAKE_REG: u32 = 256;
  constant LANDLOCK_ACCESS_FS_MAKE_SOCK (line 17) | pub const LANDLOCK_ACCESS_FS_MAKE_SOCK: u32 = 512;
  constant LANDLOCK_ACCESS_FS_MAKE_FIFO (line 18) | pub const LANDLOCK_ACCESS_FS_MAKE_FIFO: u32 = 1024;
  constant LANDLOCK_ACCESS_FS_MAKE_BLOCK (line 19) | pub const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u32 = 2048;
  constant LANDLOCK_ACCESS_FS_MAKE_SYM (line 20) | pub const LANDLOCK_ACCESS_FS_MAKE_SYM: u32 = 4096;
  constant LANDLOCK_ACCESS_FS_REFER (line 21) | pub const LANDLOCK_ACCESS_FS_REFER: u32 = 8192;
  constant LANDLOCK_ACCESS_FS_TRUNCATE (line 22) | pub const LANDLOCK_ACCESS_FS_TRUNCATE: u32 = 16384;
  constant LANDLOCK_ACCESS_FS_IOCTL_DEV (line 23) | pub const LANDLOCK_ACCESS_FS_IOCTL_DEV: u32 = 32768;
  constant LANDLOCK_ACCESS_NET_BIND_TCP (line 24) | pub const LANDLOCK_ACCESS_NET_BIND_TCP: u32 = 1;
  constant LANDLOCK_ACCESS_NET_CONNECT_TCP (line 25) | pub const LANDLOCK_ACCESS_NET_CONNECT_TCP: u32 = 2;
  constant LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (line 26) | pub const LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: u32 = 1;
  constant LANDLOCK_SCOPE_SIGNAL (line 27) | pub const LANDLOCK_SCOPE_SIGNAL: u32 = 2;
  type __s32 (line 28) | pub type __s32 = ::std::os::raw::c_int;
  type __u64 (line 29) | pub type __u64 = ::std::os::raw::c_ulonglong;
  type landlock_ruleset_attr (line 32) | pub struct landlock_ruleset_attr {
  function bindgen_test_layout_landlock_ruleset_attr (line 38) | fn bindgen_test_layout_landlock_ruleset_attr() {
  constant landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH (line 68) | pub const landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH: landlock_rule_t...
  constant landlock_rule_type_LANDLOCK_RULE_NET_PORT (line 69) | pub const landlock_rule_type_LANDLOCK_RULE_NET_PORT: landlock_rule_type ...
  type landlock_rule_type (line 70) | pub type landlock_rule_type = ::std::os::raw::c_uint;
  type landlock_path_beneath_attr (line 73) | pub struct landlock_path_beneath_attr {
  function bindgen_test_layout_landlock_path_beneath_attr (line 78) | fn bindgen_test_layout_landlock_path_beneath_attr() {
  type landlock_net_port_attr (line 105) | pub struct landlock_net_port_attr {
  function bindgen_test_layout_landlock_net_port_attr (line 110) | fn bindgen_test_layout_landlock_net_port_attr() {

FILE: src/uapi/mod.rs
  function landlock_create_ruleset (line 69) | pub unsafe fn landlock_create_ruleset(attr: *const landlock_ruleset_attr...
  function landlock_add_rule (line 75) | pub unsafe fn landlock_add_rule(ruleset_fd: c_int, rule_type: landlock_r...
  function landlock_restrict_self (line 80) | pub unsafe fn landlock_restrict_self(ruleset_fd: c_int, flags: __u32) ->...
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (215K chars).
[
  {
    "path": ".github/workflows/pages.yml",
    "chars": 1145,
    "preview": "name: GitHub Pages\n\non:\n  push:\n    branches: [ main ]\n\n  workflow_dispatch:\n\nconcurrency:\n  group: \"pages\"\n  cancel-in-"
  },
  {
    "path": ".github/workflows/rust.yml",
    "chars": 8378,
    "preview": "name: Rust checks\n\npermissions: {}\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\nenv:\n  CA"
  },
  {
    "path": ".gitignore",
    "chars": 20,
    "preview": "/Cargo.lock\n/target\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 9230,
    "preview": "# 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- Add"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1361,
    "preview": "# Contributing\n\nThanks for your interest in contributing to rust-landlock!\n\n## Testing vs kernel ABI\n\nThe Landlock funct"
  },
  {
    "path": "COPYRIGHT",
    "chars": 354,
    "preview": "Copyright 2020 Mickaël Salaün\n\nLicensed under the Apache License, Version 2.0 <LICENSE-APACHE or\nhttps://www.apache.org/"
  },
  {
    "path": "Cargo.toml",
    "chars": 594,
    "preview": "[package]\nname = \"landlock\"\nversion = \"0.4.4\"\nedition = \"2021\"\nrust-version = \"1.71\"\ndescription = \"Landlock LSM helpers"
  },
  {
    "path": "LICENSE-APACHE",
    "chars": 11345,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "LICENSE-MIT",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2020 Mickaël Salaün\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a "
  },
  {
    "path": "README.md",
    "chars": 1713,
    "preview": "# Rust Landlock library\n\nLandlock is a security feature available since Linux 5.13.\nThe goal is to enable to restrict am"
  },
  {
    "path": "examples/sandboxer.rs",
    "chars": 9270,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// This is an idiomatic Rust rewrite of a C example:\n// https://git.kerne"
  },
  {
    "path": "src/access.rs",
    "chars": 6541,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::{\n    private, AccessError, AddRuleError, AddRulesError, BitFl"
  },
  {
    "path": "src/compat.rs",
    "chars": 31202,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::{uapi, Access, CompatError};\nuse std::fmt::{self, Display, For"
  },
  {
    "path": "src/errata.rs",
    "chars": 6844,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::compat::ABI;\nuse crate::{uapi, BitFlags};\nuse enumflags2::bitf"
  },
  {
    "path": "src/errors.rs",
    "chars": 9762,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::{Access, AccessFs, AccessNet, BitFlags, HandledAccess, Private"
  },
  {
    "path": "src/fs.rs",
    "chars": 22179,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::compat::private::OptionCompatLevelMut;\nuse crate::{\n    uapi, "
  },
  {
    "path": "src/lib.rs",
    "chars": 17756,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n//! Landlock is a security feature available since Linux 5.13.\n//! The go"
  },
  {
    "path": "src/net.rs",
    "chars": 7232,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::compat::private::OptionCompatLevelMut;\nuse crate::{\n    uapi, "
  },
  {
    "path": "src/ruleset.rs",
    "chars": 44570,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::compat::private::OptionCompatLevelMut;\nuse crate::{\n    uapi, "
  },
  {
    "path": "src/scope.rs",
    "chars": 1915,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::{uapi, Access, ABI};\nuse enumflags2::{bitflags, BitFlags};\n\n//"
  },
  {
    "path": "src/uapi/bindgen.sh",
    "chars": 1089,
    "preview": "#!/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 \""
  },
  {
    "path": "src/uapi/landlock_all.rs",
    "chars": 2092,
    "preview": "/* automatically generated by rust-bindgen 0.72.1 */\n\npub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;\npub const LAND"
  },
  {
    "path": "src/uapi/landlock_i686.rs",
    "chars": 4884,
    "preview": "/* automatically generated by rust-bindgen 0.72.1 */\n\npub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;\npub const LAND"
  },
  {
    "path": "src/uapi/landlock_x86_64.rs",
    "chars": 4884,
    "preview": "/* automatically generated by rust-bindgen 0.72.1 */\n\npub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;\npub const LAND"
  },
  {
    "path": "src/uapi/mod.rs",
    "chars": 2695,
    "preview": "// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// Use architecture-specific bindings for native x86_64 and x86 architect"
  }
]

About this extraction

This page contains the full source code of the landlock-lsm/rust-landlock GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (203.2 KB), approximately 51.0k tokens, and a symbol index with 314 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!