Skip to content

How exactly should wasi:filesystem open-flags and descriptor-flags interact with native open modes/flags? #936

Open

Description

@alexcrichton

Currently in Wasmtime it's possible to get possibly-surprising behavior with opening files. This program:

fn main() {
    let dirs = wasip2::filesystem::preopens::get_directories();
    let (dir, _) = &dirs[0];
    let f = dir
        .open_at(
            wasip2::filesystem::types::PathFlags::empty(),
            "hi.txt",
            wasip2::filesystem::types::OpenFlags::CREATE,
            wasip2::filesystem::types::DescriptorFlags::empty(),
        )
        .unwrap();
    dbg!(f.get_flags());
}

outputs:

[src/main.rs:12:5] f.get_flags() = Ok(
    DescriptorFlags(
        READ | WRITE,
    ),
)

Where this is an example where open_at did not request any flags, yet the descriptor is both readable and writable. This behavior is additionally part of wasi-testsuite, but my hunch is that it's mostly derivative of Wasmtime's current implementation which isn't authoritative. Wasmtime's own implementation is derivative of Rust's own OpenOptions::create method which requires that when creating files write is used as a permission.

Currently the documentation of open-flags::create is that it's similar to O_CREAT, but this doesn't mention any relationship with read/write flags/permissions. Additionally my local man page for the open syscalls says that one of O_{RDONLY,WRONLY,RDWR} must be specified, but WASI's open-at doesn't currently have the same restriction.

Overall I don't feel that there's an "obviously correct" answer here so I'm seeking guidance from the upstream specification itself here. What should the precise behavior be for all these flags when dealing with filesystem methods? For reference Wasmtime's implementation is here and this is where OpenFlags::CREATE turns into a request for writability too.

Activity

  1. badeend commented on Jul 2, 2026

    @badeend
    Member

    My take: the observed effective flags of a fd should always be equal to (or a subset of) the flags requested at time of opening the fd. The guest shouldn't be able to magically gain additional read/write permissions.


    my local man page for the open syscalls says that one of O_{RDONLY,WRONLY,RDWR} must be specified

    Traditionally, that was indeed the case. Though one could argue that the relatively new-ish O_PATH option supported on Linux & FreeBSD is the spiritual equivalent of opening a file without read nor write permissions. I recall that Linux ignores the O_{RDONLY,WRONLY,RDWR} flags when O_PATH is provided, but I can not find that documented on their man page.

    WASI's open-at doesn't currently have the same restriction

    IIUC based on wasmtime's implementation; if none of the read or write flags are requested by the guest, the file is still opened with O_RDONLY (because as you pointed out the OS requires one of these flags), but read_via_stream fails with NotPermitted.

  2. bbyalcinkaya commented on Sep 28, 2026

    @bbyalcinkaya

    A cross-engine data point, I tested the above program with jco, the alternative p2 runtime listed here, and it behaves differently from Wasmtime. jco returns empty flags:

    [main.rs:12:5] f.get_flags() = Ok(
        DescriptorFlags(
            0x0,
        ),
    )
    

    I also tried an explicit write through such a descriptor:

    fn main() {
        let dirs = wasi::filesystem::preopens::get_directories();
        let (dir, _) = &dirs[0];
        let f = dir
            .open_at(
                wasi::filesystem::types::PathFlags::empty(),
                "tmp/hi.txt",
                wasi::filesystem::types::OpenFlags::CREATE,
                wasi::filesystem::types::DescriptorFlags::empty(),
            )
            .expect("open-at failed");
    
        f.write(b"this write should have been rejected\n", 0)
            .expect("write failed");
    }

    Wasmtime writes to the file successfully (if it already exists, updates the file). jco rejects the write:

    [Error: EBADF: bad file descriptor, write] {
      errno: -9,
      code: 'EBADF',
      syscall: 'write'
    }
    

    So the two p2 implementations disagree here. Wasmtime grants a writable descriptor for a CREATE open with no requested descriptor-flags, while jco does not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P-filesystemProposal: wasi-filesystem

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions