Repository: tetratelabs/wazero
Pull request #1237 (2023-03-14 04:21:34)

returns EBADF when attempting to write to STDIN
Found this problem while troubleshooting #1232 because fd zero == stdin, so a logic error can show up as a problem writing to stdin.


Patch:
diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go
index d9a47aba..a8603f5c 100644
--- a/imports/wasi_snapshot_preview1/fs.go
+++ b/imports/wasi_snapshot_preview1/fs.go
@@ -1295,8 +1295,9 @@ func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) Errno {
 		offset := int64(params[3])
 		writer = sysfs.WriterAtOffset(f.File, offset)
 		resultNwritten = uint32(params[4])
+	} else if writer, ok = f.File.(io.Writer); !ok {
+		return ErrnoBadf
 	} else {
-		writer = f.File.(io.Writer)
 		resultNwritten = uint32(params[3])
 	}
 
diff --git a/internal/gojs/fs.go b/internal/gojs/fs.go
index c57d0c67..35df7bba 100644
--- a/internal/gojs/fs.go
+++ b/internal/gojs/fs.go
@@ -297,8 +297,12 @@ func syscallWrite(mod api.Module, fd uint32, offset interface{}, p []byte) (n ui
 		err = syscall.EBADF
 	} else if offset != nil {
 		writer = sysfs.WriterAtOffset(f.File, toInt64(offset))
-	} else {
-		writer = f.File.(io.Writer)
+	} else if writer, ok = f.File.(io.Writer); !ok {
+		err = syscall.EBADF
+	}
+
+	if err != nil {
+		return
 	}
 
 	if nWritten, e := writer.Write(p); e == nil || e == io.EOF {
diff --git a/internal/sys/fs.go b/internal/sys/fs.go
index b8f60ad4..46feff9a 100644
--- a/internal/sys/fs.go
+++ b/internal/sys/fs.go
@@ -476,8 +476,10 @@ func (c *FSContext) Close(context.Context) (err error) {
 // WriterForFile returns a writer for the given file descriptor or nil if not
 // opened or not writeable (e.g. a directory or a file not opened for writes).
 func WriterForFile(fsc *FSContext, fd uint32) (writer io.Writer) {
-	if f, ok := fsc.LookupFile(fd); ok {
-		writer = f.File.(io.Writer)
+	if f, ok := fsc.LookupFile(fd); !ok {
+		return
+	} else if w, ok := f.File.(io.Writer); ok {
+		writer = w
 	}
 	return
 }


Test patch:
diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go
index 230a7496..7550bf46 100644
--- a/imports/wasi_snapshot_preview1/fs_test.go
+++ b/imports/wasi_snapshot_preview1/fs_test.go
@@ -2860,6 +2860,12 @@ func Test_fdWrite_Errors(t *testing.T) {
 <== (nwritten=,errno=EBADF)
 `,
 		},
+		{
+			name:          "not writable FD",
+			fd:            sys.FdStdin,
+			expectedErrno: ErrnoBadf,
+			expectedLog:   "\n", // stdin is not sampled
+		},
 		{
 			name:          "out-of-memory reading iovs[0].offset",
 			fd:            fd,
diff --git a/internal/gojs/testdata/stdio/main.go b/internal/gojs/testdata/stdio/main.go
index ec0a563c..c894f925 100644
--- a/internal/gojs/testdata/stdio/main.go
+++ b/internal/gojs/testdata/stdio/main.go
@@ -1,9 +1,11 @@
 package stdio
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"os"
+	"syscall"
 )
 
 func Main() {
@@ -12,6 +14,9 @@ func Main() {
 		panic(err)
 	}
 
+	if _, err = fmt.Fprintln(os.Stdin, " "); errors.Unwrap(err) != syscall.EBADF {
+		panic(fmt.Sprint(err.Error(), "!=", syscall.EBADF))
+	}
 	printToFile("stdout", os.Stdout, len(b))
 	printToFile("stderr", os.Stderr, len(b))
 }
diff --git a/internal/sys/fs_test.go b/internal/sys/fs_test.go
index 82fccd8f..b35de3bf 100644
--- a/internal/sys/fs_test.go
+++ b/internal/sys/fs_test.go
@@ -347,3 +347,13 @@ func TestFSContext_ChangeOpenFlag(t *testing.T) {
 	require.True(t, ok)
 	require.Equal(t, f2.openFlag&syscall.O_APPEND, 0)
 }
+
+func TestWriterForFile(t *testing.T) {
+	testFS, err := NewFSContext(nil, nil, nil, sysfs.UnimplementedFS{})
+	require.NoError(t, err)
+
+	require.Nil(t, WriterForFile(testFS, FdStdin))
+	require.Equal(t, noopStdout.File, WriterForFile(testFS, FdStdout))
+	require.Equal(t, noopStderr.File, WriterForFile(testFS, FdStderr))
+	require.Nil(t, WriterForFile(testFS, FdPreopen))
+}