os: fix Root test failures on AIX

AIX has two behaviours that differ from other platforms, causing
test failures introduced by the CVE-2026-39822 fix (CL 797661).

Paths ending in / on non-directory targets may succeed on AIX
instead of returning ENOTDIR. This affects opening a regular
file as "target/" and following a symlink whose stored target
ends in "/" (e.g. "link -> file/"). Skip the affected
consistency test cases on AIX.

AIX allows read(2) on a directory file descriptor, returning
directory bytes instead of EISDIR. This causes root.ReadFile
on a directory target to succeed with err=nil. AIX is added
to the exception similar to NetBSD.

Neither change affects the security fix; AIX was not vulnerable
to the original issue because it does not follow symlinks through
a trailing slash when O_NOFOLLOW is set.

Fixes #80382

Change-Id: I8ddb3bc841c3d94bfeb91659dc6b3216a6a0d992
Reviewed-on: https://go-review.googlesource.com/c/go/+/840085
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/src/os/root_test.go b/src/os/root_test.go
index 8d20ba0..c947edf 100644
--- a/src/os/root_test.go
+++ b/src/os/root_test.go
@@ -1139,6 +1139,13 @@
 		// os.Create returns ENOTDIR or EISDIR depending on the platform.
 		return runtime.GOOS == "js"
 	},
+	check: func(t *testing.T) {
+		if runtime.GOOS == "aix" {
+			// On AIX, opening a regular file with a trailing slash succeeds,
+			// whereas os.Root returns ENOTDIR.
+			t.Skip("known inconsistency on aix")
+		}
+	},
 }, {
 	name: "file in path",
 	fs: []string{
@@ -1210,6 +1217,13 @@
 		"link => file/",
 	},
 	open: "link",
+	check: func(t *testing.T) {
+		if runtime.GOOS == "aix" {
+			// On AIX, a symlink whose stored target ends in a slash is
+			// followed successfully, whereas os.Root returns ENOTDIR.
+			t.Skip("known inconsistency on aix")
+		}
+	},
 }, {
 	name: "long file name",
 	open: strings.Repeat("a", 500),
@@ -2273,6 +2287,15 @@
 		}
 	}
 
+	if runtime.GOOS == "aix" {
+		// On AIX, paths ending in / on non-directory targets may succeed
+		// instead of returning ENOTDIR, so root and non-root results
+		// are inconsistent. See https://go.dev/issue/80382.
+		if rootTest.source.anySlashSuffix() || rootTest.target.anySlashSuffix() {
+			return
+		}
+	}
+
 	osResult, osErr := f(t, osTest)
 
 	t.Cleanup(func() {
@@ -3279,9 +3302,9 @@
 		case runtime.GOOS == "plan9":
 			// Plan9 lets you read from directories.
 			// Just rely on consistency checks.
-		case runtime.GOOS == "netbsd":
+		case runtime.GOOS == "netbsd", runtime.GOOS == "aix":
 			// See https://go.dev/issue/80322:
-			// NetBSD builder appears to be succeeding on read-from-dir as well.
+			// NetBSD and AIX builders appear to be succeeding on read-from-dir as well.
 			return "", gotErr
 		case test.target.finalKind() == testFileDir:
 			test.wantError(t, gotErr, errAny)