Repository: tetratelabs/wazero (go)

Issue:
Should not be able to open a directory with write
**Describe the bug**

`path_open`ing a directory with `rights::fd_write` should fail. Wazero does fail *if* the `oflags::directory` flag is not set. But will successfully open the directory and return a valid fd if the it is set.

**To Reproduce**

This Rust snippet can be compiled with Cargo and `wasi` crate v0.11. Wazero returns an FD while other runtimes (Wasmtime, Wasmer, WasmEdge, WAMR, and Node) all fails the call.

```rust
fn main() {
    unsafe {
        let base_fd = 3;
        let fd = wasi::path_open(
            base_fd,
            0,
            ".",
            wasi::OFLAGS_DIRECTORY,
            wasi::RIGHTS_FD_WRITE,
            0,
            0,
        )
        .unwrap();

        eprintln!("fd {fd}");
    }
}
```

**Environment (please complete the relevant information):**
- Go version: 1.22.3
- wazero Version: 5c8366f8d5f8f8e355c241c423b254eb1c10b02b
- Host architecture: amd64
- OS: Linux
- Runtime mode: compiler


Pull request description:
wasi: `path_open` should accept a dir with RIGHT_FD_WRITE
This commit fixes a `path_open` behavior that allows a directory to be `path_open`ed with the `rights::fd_write` flag.  Since Wazero considers it to be an error to open a directory with the write flag normally, it is safe to treat this as a bug.  Although rights/capability system is being scrapped, we still use the `fd_read` and `fd_write` flag to determine if write permission needs to be set.

fixes #2243

Patch:
diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go
index 7575d77f..1ec0d81b 100644
--- a/imports/wasi_snapshot_preview1/fs.go
+++ b/imports/wasi_snapshot_preview1/fs.go
@@ -1708,7 +1708,6 @@ func openFlags(dirflags, oflags, fdflags uint16, rights uint32) (openFlags exper
 	}
 	if oflags&wasip1.O_DIRECTORY != 0 {
 		openFlags |= experimentalsys.O_DIRECTORY
-		return // Early return for directories as the rest of flags doesn't make sense for it.
 	} else if oflags&wasip1.O_EXCL != 0 {
 		openFlags |= experimentalsys.O_EXCL
 	}
diff --git a/internal/engine/wazevo/ssa/builder.go b/internal/engine/wazevo/ssa/builder.go
index 316e6261..0b700c4b 100644
--- a/internal/engine/wazevo/ssa/builder.go
+++ b/internal/engine/wazevo/ssa/builder.go
@@ -518,21 +518,42 @@ func (b *builder) findValue(typ Type, variable Variable, blk *basicBlock) Value
 	// If this block has multiple predecessors, we have to gather the definitions,
 	// and treat them as an argument to this block.
 	//
-	// The first thing is to define a new parameter to this block which may or may not be redundant, but
-	// later we eliminate trivial params in an optimization pass. This must be done before finding the
-	// definitions in the predecessors so that we can break the cycle.
-	paramValue := blk.AddParam(b, typ)
-	b.DefineVariable(variable, paramValue, blk)
-
-	// After the new param is added, we have to manipulate the original branching instructions
-	// in predecessors so that they would pass the definition of `variable` as the argument to
-	// the newly added PHI.
+	// But before that, we have to check if the possible definitions are the same Value.
+	tmpValue := b.allocateValue(typ)
+	// Break the cycle by defining the variable with the tmpValue.
+	b.DefineVariable(variable, tmpValue, blk)
+	// Check all the predecessors if they have the same definition.
+	uniqueValue := ValueInvalid
 	for i := range blk.preds {
-		pred := &blk.preds[i]
-		value := b.findValue(typ, variable, pred.blk)
-		pred.branch.addArgumentBranchInst(b, value)
+		predValue := b.findValue(typ, variable, blk.preds[i].blk)
+		if uniqueValue == ValueInvalid {
+			uniqueValue = predValue
+		} else if uniqueValue != predValue {
+			uniqueValue = ValueInvalid
+			break
+		}
+	}
+
+	if uniqueValue != ValueInvalid {
+		// If all the predecessors have the same definition, we can use that value.
+		b.DefineVariable(variable, uniqueValue, blk)
+		b.alias(tmpValue, uniqueValue)
+		return uniqueValue
+	} else {
+		// Otherwise, add the tmpValue to this block as a parameter which may or may not be redundant, but
+		// later we eliminate trivial params in an optimization pass. This must be done before finding the
+		// definitions in the predecessors so that we can break the cycle.
+		blk.addParamOn(tmpValue)
+		// After the new param is added, we have to manipulate the original branching instructions
+		// in predecessors so that they would pass the definition of `variable` as the argument to
+		// the newly added PHI.
+		for i := range blk.preds {
+			pred := &blk.preds[i]
+			value := b.findValue(typ, variable, pred.blk)
+			pred.branch.addArgumentBranchInst(b, value)
+		}
+		return tmpValue
 	}
-	return paramValue
 }
 
 // Seal implements Builder.Seal.
diff --git a/internal/sysfs/file.go b/internal/sysfs/file.go
index 9a77205b..fdbf1fde 100644
--- a/internal/sysfs/file.go
+++ b/internal/sysfs/file.go
@@ -38,9 +38,6 @@ func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) {
 }
 
 func OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (*os.File, experimentalsys.Errno) {
-	if flag&experimentalsys.O_DIRECTORY != 0 && flag&(experimentalsys.O_WRONLY|experimentalsys.O_RDWR) != 0 {
-		return nil, experimentalsys.EISDIR // invalid to open a directory writeable
-	}
 	return openFile(path, flag, perm)
 }
 
diff --git a/site/content/community/users.md b/site/content/community/users.md
index 8cfefc96..13f65a68 100644
--- a/site/content/community/users.md
+++ b/site/content/community/users.md
@@ -66,6 +66,13 @@ considering their efforts before starting your own!
 | [YoMo][26]    | Streaming [Serverless][27] Framework for buildnig Geo-distributed system  |
 | [Wetware][28] | Web3's answer to Cloud hosting                                            |
 
+### Database
+
+| Name          | Description                              |
+|:--------------|------------------------------------------|
+| [wescale][44] | a database proxy that supports OnlineDDL |
+
+
 ## Updating this list
 
 This is a community maintained list. It may have an inaccurate or outdated
@@ -160,3 +167,5 @@ experience.
 [42]: https://github.com/gen2brain/jpegli
 
 [43]: https://github.com/gen2brain/webp
+
+[44]: https://github.com/wesql/wescale


Test patch:
diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go
index 03c324f9..b87212f4 100644
--- a/imports/wasi_snapshot_preview1/fs_test.go
+++ b/imports/wasi_snapshot_preview1/fs_test.go
@@ -3937,6 +3937,30 @@ func Test_pathOpen(t *testing.T) {
 			expectedLog: `
 ==> 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)
+`,
+		},
+		{
+			name:          "sysfs.DirFS file O_DIRECTORY RIGHTS_FD_WRITE",
+			fs:            writeFS,
+			path:          func(*testing.T) string { return fileName },
+			oflags:        wasip1.O_DIRECTORY,
+			rights:        wasip1.RIGHT_FD_WRITE,
+			expectedErrno: wasip1.ErrnoNotdir,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=DIRECTORY,fs_rights_base=FD_WRITE,fs_rights_inheriting=,fdflags=)
+<== (opened_fd=,errno=ENOTDIR)
+`,
+		},
+		{
+			name:          "sysfs.DirFS dir O_DIRECTORY RIGHTS_FD_WRITE",
+			fs:            writeFS,
+			path:          func(*testing.T) string { return dirName },
+			oflags:        wasip1.O_DIRECTORY,
+			rights:        wasip1.RIGHT_FD_WRITE,
+			expectedErrno: wasip1.ErrnoIsdir,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=DIRECTORY,fs_rights_base=FD_WRITE,fs_rights_inheriting=,fdflags=)
+<== (opened_fd=,errno=EISDIR)
 `,
 		},
 	}
diff --git a/internal/engine/wazevo/frontend/frontend_test.go b/internal/engine/wazevo/frontend/frontend_test.go
index d1040a4a..b982556d 100644
--- a/internal/engine/wazevo/frontend/frontend_test.go
+++ b/internal/engine/wazevo/frontend/frontend_test.go
@@ -1058,14 +1058,14 @@ blk1: () <-- (blk0)
 	Call f1:sig1, exec_ctx, module_ctx
 	v5:i64 = Load module_ctx, 0x8
 	v6:i64 = Uload32 module_ctx, 0x10
-	Jump blk3, v2
+	Jump blk3
 
 blk2: () <-- (blk0)
-	Jump blk3, v2
+	Jump blk3
 
-blk3: (v7:i32) <-- (blk1,blk2)
+blk3: () <-- (blk1,blk2)
 	v8:i64 = Iconst_64 0x4
-	v9:i64 = UExtend v7, 32->64
+	v9:i64 = UExtend v2, 32->64
 	v10:i64 = Uload32 module_ctx, 0x10
 	v11:i64 = Iadd v9, v8
 	v12:i32 = Icmp lt_u, v10, v11
@@ -2869,7 +2869,7 @@ blk0: (exec_ctx:i64, module_ctx:i64, v2:i32)
 
 blk1: () <-- (blk0)
 	v11:i32 = Load v9, 0x10
-	Jump blk3, v2
+	Jump blk3
 
 blk2: () <-- (blk0)
 	v12:i32 = Load v9, 0x10
@@ -2880,18 +2880,13 @@ blk2: () <-- (blk0)
 	ExitIfTrue v16, exec_ctx, memory_out_of_bounds
 	v17:i32 = Load v9, 0x30
 	v18:i32 = Load v9, 0x25
-	Jump blk3, v2
+	Jump blk3
 
-blk3: (v19:i32) <-- (blk1,blk2)
-	v20:i64 = Iconst_64 0x19
-	v21:i64 = UExtend v19, 32->64
-	v22:i64 = Uload32 module_ctx, 0x10
-	v23:i64 = Iadd v21, v20
-	v24:i32 = Icmp lt_u, v22, v23
-	ExitIfTrue v24, exec_ctx, memory_out_of_bounds
-	v25:i64 = Load module_ctx, 0x8
-	v26:i64 = Iadd v25, v21
-	v27:i32 = Load v26, 0x15
+blk3: () <-- (blk1,blk2)
+	v20:i64 = Load module_ctx, 0x8
+	v21:i64 = UExtend v2, 32->64
+	v22:i64 = Iadd v20, v21
+	v23:i32 = Load v22, 0x15
 	Jump blk_ret
 `,
 		},