wasip1: fix file open modes used by wasi-libc (#1421)

This extends wazero's interpretation of rights in wasi_snapshot_preview1.path_open to allow programs to open files in either read-only, write-only, or read-write mode.

Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
Co-authored-by: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com>
This commit is contained in:
Achille
2023-05-01 18:48:26 -07:00
committed by GitHub
parent 20017ca5e6
commit d5a2d3c7b4
9 changed files with 203 additions and 44 deletions

View File

@@ -327,8 +327,8 @@ func TestRun(t *testing.T) {
==> wasi_snapshot_preview1.fd_prestat_get(fd=4)
<== (prestat=,errno=EBADF)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=bear.txt,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=FD_DATASYNC|FDSTAT_SET_FLAGS|FD_SYNC|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK,fs_rights_inheriting=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=bear.txt,oflags=,fs_rights_base=FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_ADVISE|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK|PATH_RENAME_SOURCE|PATH_RENAME_TARGET|PATH_FILESTAT_GET|PATH_FILESTAT_SET_SIZE|PATH_FILESTAT_SET_TIMES|FD_FILESTAT_GET|FD_FILESTAT_SET_TIMES|PATH_SYMLINK|PATH_REMOVE_DIRECTORY|PATH_UNLINK_FILE|POLL_FD_READWRITE,fs_rights_inheriting=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK|PATH_RENAME_SOURCE|PATH_RENAME_TARGET|PATH_FILESTAT_GET|PATH_FILESTAT_SET_SIZE|PATH_FILESTAT_SET_TIMES|FD_FILESTAT_GET|FD_FILESTAT_SET_SIZE|FD_FILESTAT_SET_TIMES|PATH_SYMLINK|PATH_REMOVE_DIRECTORY|PATH_UNLINK_FILE|POLL_FD_READWRITE,fdflags=)
<== (opened_fd=4,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_filestat_get(fd=4)
<== (filestat={filetype=REGULAR_FILE,size=5,mtim=%d},errno=ESUCCESS)

View File

@@ -222,24 +222,66 @@ func fdFdstatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.E
fdflags = wasip1.FD_APPEND
}
filetype := getWasiFiletype(st.Mode)
writeFdstat(buf, filetype, fdflags)
var fsRightsBase uint32
var fsRightsInheriting uint32
fileType := getWasiFiletype(st.Mode)
switch fileType {
case wasip1.FILETYPE_DIRECTORY:
// To satisfy wasi-testsuite, we must advertise that directories cannot
// be given seek permission (RIGHT_FD_SEEK).
fsRightsBase = dirRightsBase
fsRightsInheriting = fileRightsBase | dirRightsBase
default:
fsRightsBase = fileRightsBase
}
writeFdstat(buf, fileType, fdflags, fsRightsBase, fsRightsInheriting)
return 0
}
var blockFdstat = []byte{
wasip1.FILETYPE_BLOCK_DEVICE, 0, // filetype
0, 0, 0, 0, 0, 0, // fdflags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
}
const fileRightsBase = wasip1.RIGHT_FD_DATASYNC |
wasip1.RIGHT_FD_READ |
wasip1.RIGHT_FD_SEEK |
wasip1.RIGHT_FDSTAT_SET_FLAGS |
wasip1.RIGHT_FD_SYNC |
wasip1.RIGHT_FD_TELL |
wasip1.RIGHT_FD_WRITE |
wasip1.RIGHT_FD_ADVISE |
wasip1.RIGHT_FD_ALLOCATE |
wasip1.RIGHT_FD_FILESTAT_GET |
wasip1.RIGHT_FD_FILESTAT_SET_SIZE |
wasip1.RIGHT_FD_FILESTAT_SET_TIMES |
wasip1.RIGHT_POLL_FD_READWRITE
func writeFdstat(buf []byte, filetype uint8, fdflags uint16) {
// memory is re-used, so ensure the result is defaulted.
copy(buf, blockFdstat)
buf[0] = filetype
buf[2] = byte(fdflags)
const dirRightsBase = wasip1.RIGHT_FD_DATASYNC |
wasip1.RIGHT_FDSTAT_SET_FLAGS |
wasip1.RIGHT_FD_SYNC |
wasip1.RIGHT_PATH_CREATE_DIRECTORY |
wasip1.RIGHT_PATH_CREATE_FILE |
wasip1.RIGHT_PATH_LINK_SOURCE |
wasip1.RIGHT_PATH_LINK_TARGET |
wasip1.RIGHT_PATH_OPEN |
wasip1.RIGHT_FD_READDIR |
wasip1.RIGHT_PATH_READLINK |
wasip1.RIGHT_PATH_RENAME_SOURCE |
wasip1.RIGHT_PATH_RENAME_TARGET |
wasip1.RIGHT_PATH_FILESTAT_GET |
wasip1.RIGHT_PATH_FILESTAT_SET_SIZE |
wasip1.RIGHT_PATH_FILESTAT_SET_TIMES |
wasip1.RIGHT_FD_FILESTAT_GET |
wasip1.RIGHT_FD_FILESTAT_SET_TIMES |
wasip1.RIGHT_PATH_SYMLINK |
wasip1.RIGHT_PATH_REMOVE_DIRECTORY |
wasip1.RIGHT_PATH_UNLINK_FILE
func writeFdstat(buf []byte, fileType uint8, fdflags uint16, fsRightsBase, fsRightsInheriting uint32) {
b := (*[24]byte)(buf)
le.PutUint16(b[0:], uint16(fileType))
le.PutUint16(b[2:], fdflags)
le.PutUint32(b[4:], 0)
le.PutUint64(b[8:], uint64(fsRightsBase))
le.PutUint64(b[16:], uint64(fsRightsInheriting))
}
// fdFdstatSetFlags is the WASI function named FdFdstatSetFlagsName which
@@ -1713,24 +1755,42 @@ func openFlags(dirflags, oflags, fdflags uint16, rights uint32) (openFlags int)
} else if oflags&wasip1.O_EXCL != 0 {
openFlags |= syscall.O_EXCL
}
// Because we don't implement rights, we paritally rely on the open flags
// to determine the mode in which the file will be opened. This will create
// divergeant behavior compared to WASI runtimes which have a more strict
// interpretation of the WASI capabilities model; for example, a program
// which sets O_CREAT but does not give read or write permissions will
// successfully create a file when running with wazero, but might get a
// permission denied error on other runtimes.
defaultMode := syscall.O_RDONLY
if oflags&wasip1.O_TRUNC != 0 {
openFlags |= syscall.O_RDWR | syscall.O_TRUNC
openFlags |= syscall.O_TRUNC
defaultMode = syscall.O_RDWR
}
if oflags&wasip1.O_CREAT != 0 {
openFlags |= syscall.O_RDWR | syscall.O_CREAT
openFlags |= syscall.O_CREAT
defaultMode = syscall.O_RDWR
}
if fdflags&wasip1.FD_APPEND != 0 {
openFlags |= syscall.O_RDWR | syscall.O_APPEND
openFlags |= syscall.O_APPEND
defaultMode = syscall.O_RDWR
}
// Since rights were discontinued in wasi, we only interpret RIGHT_FD_WRITE
// because it is the only way to know that we need to set write permissions
// on a file if the application did not pass any of O_CREATE, O_APPEND, nor
// O_TRUNC.
if rights&wasip1.RIGHT_FD_WRITE != 0 {
const r = wasip1.RIGHT_FD_READ
const w = wasip1.RIGHT_FD_WRITE
const rw = r | w
switch {
case (rights & rw) == rw:
openFlags |= syscall.O_RDWR
}
if openFlags == 0 {
openFlags = syscall.O_RDONLY
case (rights & w) == w:
openFlags |= syscall.O_WRONLY
case (rights & r) == r:
openFlags |= syscall.O_RDONLY
default:
openFlags |= defaultMode
}
return
}

View File

@@ -248,12 +248,12 @@ func Test_fdFdstatGet(t *testing.T) {
expectedMemory: []byte{
1, 0, // fs_filetype
0, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=0)
<== (stat={filetype=BLOCK_DEVICE,fdflags=,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
<== (stat={filetype=BLOCK_DEVICE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
`,
},
{
@@ -262,12 +262,12 @@ func Test_fdFdstatGet(t *testing.T) {
expectedMemory: []byte{
1, 0, // fs_filetype
1, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=1)
<== (stat={filetype=BLOCK_DEVICE,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
<== (stat={filetype=BLOCK_DEVICE,fdflags=APPEND,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
`,
},
{
@@ -276,12 +276,12 @@ func Test_fdFdstatGet(t *testing.T) {
expectedMemory: []byte{
1, 0, // fs_filetype
1, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=2)
<== (stat={filetype=BLOCK_DEVICE,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
<== (stat={filetype=BLOCK_DEVICE,fdflags=APPEND,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
`,
},
{
@@ -290,12 +290,12 @@ func Test_fdFdstatGet(t *testing.T) {
expectedMemory: []byte{
3, 0, // fs_filetype
0, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
0x19, 0xfe, 0xbf, 0x7, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
0xff, 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, 0x0, // fs_rights_inheriting
},
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
<== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=FD_DATASYNC|FDSTAT_SET_FLAGS|FD_SYNC|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK,fs_rights_inheriting=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK},errno=ESUCCESS)
`,
},
{
@@ -304,12 +304,12 @@ func Test_fdFdstatGet(t *testing.T) {
expectedMemory: []byte{
4, 0, // fs_filetype
0, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=4)
<== (stat={filetype=REGULAR_FILE,fdflags=,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
<== (stat={filetype=REGULAR_FILE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
`,
},
{
@@ -318,12 +318,12 @@ func Test_fdFdstatGet(t *testing.T) {
expectedMemory: []byte{
3, 0, // fs_filetype
0, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
0x19, 0xfe, 0xbf, 0x7, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
0xff, 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, 0x0, // fs_rights_inheriting
},
expectedLog: `
==> wasi_snapshot_preview1.fd_fdstat_get(fd=5)
<== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
<== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=FD_DATASYNC|FDSTAT_SET_FLAGS|FD_SYNC|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK,fs_rights_inheriting=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK},errno=ESUCCESS)
`,
},
{
@@ -3936,16 +3936,16 @@ func Test_pathOpen(t *testing.T) {
`,
},
{
name: "sysfs.DirFS RIGHT_FD_WRITE",
name: "sysfs.DirFS RIGHT_FD_READ|RIGHT_FD_WRITE",
fs: writeFS,
path: func(*testing.T) string { return fileName },
oflags: 0,
rights: wasip1.RIGHT_FD_WRITE,
rights: wasip1.RIGHT_FD_READ | wasip1.RIGHT_FD_WRITE,
expected: func(t *testing.T, fsc *sys.FSContext) {
requireContents(t, fsc, expectedOpenedFd, fileName, fileContents)
},
expectedLog: `
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=,fs_rights_base=FD_WRITE,fs_rights_inheriting=,fdflags=)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=,fs_rights_base=FD_READ|FD_WRITE,fs_rights_inheriting=,fdflags=)
<== (opened_fd=4,errno=ESUCCESS)
`,
},

View File

@@ -413,9 +413,19 @@ func Test_openFlags(t *testing.T) {
dirflags: wasip1.LOOKUP_SYMLINK_FOLLOW,
expectedOpenFlags: syscall.O_RDONLY,
},
{
name: "rights=FD_READ",
rights: wasip1.RIGHT_FD_READ,
expectedOpenFlags: platform.O_NOFOLLOW | syscall.O_RDONLY,
},
{
name: "rights=FD_WRITE",
rights: wasip1.RIGHT_FD_WRITE,
expectedOpenFlags: platform.O_NOFOLLOW | syscall.O_WRONLY,
},
{
name: "rights=FD_READ|FD_WRITE",
rights: wasip1.RIGHT_FD_READ | wasip1.RIGHT_FD_WRITE,
expectedOpenFlags: platform.O_NOFOLLOW | syscall.O_RDWR,
},
}

View File

@@ -1,5 +1,6 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
@@ -70,6 +71,54 @@ void main_sleepmillis(int millis) {
printf("OK\n");
}
void main_open_rdonly() {
const char *path = "zig-cc.rdonly.test";
int fd;
char buf[32];
fd = open(path, O_CREAT|O_TRUNC|O_RDONLY, 0644);
if (fd < 0) {
perror("ERR: open");
goto cleanup;
}
if (write(fd, "hello world\n", 12) >= 0) {
perror("ERR: write");
goto cleanup;
}
if (read(fd, buf, sizeof(buf)) != 0) {
perror("ERR: read");
goto cleanup;
}
puts("OK");
cleanup:
close(fd);
unlink(path);
}
void main_open_wronly() {
const char *path = "zig-cc.wronly.test";
int fd;
char buf[32];
fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644);
if (fd < 0) {
perror("ERR: open");
goto cleanup;
}
if (write(fd, "hello world\n", 12) != 12) {
perror("ERR: write");
goto cleanup;
}
if (read(fd, buf, sizeof(buf)) >= 0) {
perror("ERR: read");
goto cleanup;
}
puts("OK");
cleanup:
close(fd);
unlink(path);
}
int main(int argc, char** argv) {
if (strcmp(argv[1],"ls")==0) {
bool repeat = false;
@@ -95,6 +144,11 @@ int main(int argc, char** argv) {
timeout = atoi(argv[2]);
}
main_sleepmillis(timeout);
} else if (strcmp(argv[1],"open-rdonly")==0) {
main_open_rdonly();
} else if (strcmp(argv[1],"open-wronly")==0) {
main_open_wronly();
} else {
fprintf(stderr, "unknown command: %s\n", argv[1]);

View File

@@ -311,3 +311,35 @@ func Test_Sleep(t *testing.T) {
require.True(t, time.Since(start) >= 100*time.Millisecond)
require.Equal(t, "OK\n", console)
}
func Test_open(t *testing.T) {
for toolchain, bin := range map[string][]byte{
"zig-cc": wasmZigCc,
} {
toolchain := toolchain
bin := bin
t.Run(toolchain, func(t *testing.T) {
testOpenReadOnly(t, bin)
testOpenWriteOnly(t, bin)
})
}
}
func testOpenReadOnly(t *testing.T, bin []byte) {
testOpen(t, "rdonly", bin)
}
func testOpenWriteOnly(t *testing.T, bin []byte) {
testOpen(t, "wronly", bin)
}
func testOpen(t *testing.T, cmd string, bin []byte) {
t.Run(cmd, func(t *testing.T) {
moduleConfig := wazero.NewModuleConfig().
WithArgs("wasi", "open-"+cmd).
WithFSConfig(wazero.NewFSConfig().WithDirMount(t.TempDir(), "/"))
console := compileAndRun(t, moduleConfig, bin)
require.Equal(t, "OK", strings.TrimSpace(console))
})
}

View File

@@ -87,8 +87,14 @@ func isSymlink(path string) bool {
return false
}
// The following is lifted from syscall_windows.go to add support for setting FILE_SHARE_DELETE.
// https://github.com/golang/go/blob/go1.20/src/syscall/syscall_windows.go#L308-L379
// # Differences from syscall.Open
//
// This code is based on syscall.Open from the below link with some differences
// https://github.com/golang/go/blame/go1.20/src/syscall/syscall_windows.go#L308-L379
//
// - syscall.O_CREAT doesn't imply syscall.GENERIC_WRITE as that breaks
// flag expectations in wasi.
// - add support for setting FILE_SHARE_DELETE.
func open(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
if len(path) == 0 {
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
@@ -106,9 +112,6 @@ func open(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
case syscall.O_RDWR:
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
}
if mode&syscall.O_CREAT != 0 {
access |= syscall.GENERIC_WRITE
}
if mode&syscall.O_APPEND != 0 {
access &^= syscall.GENERIC_WRITE
access |= syscall.FILE_APPEND_DATA