os/user: report an unknown Windows account as UnknownUserError

Lookup documents that a user who cannot be found is reported as
UnknownUserError, and LookupGroup says the same for UnknownGroupError.
On Windows both resolve names through LookupAccountName, which fails
with ERROR_NONE_MAPPED when a name maps to no account at all, and that
raw syscall.Errno reached the caller unchanged.

Map it at the two name lookup sites. The Windows error is wrapped
rather than replaced so that callers already testing for
ERROR_NONE_MAPPED keep matching it.

Nothing on Windows returned these types before, so no existing result
changes. One difference from the other platforms is worth noting: they
return the bare error type, while this returns a wrapper, so errors.As
finds it and a direct type assertion does not.

Fixes #73595

Change-Id: I6f38d47baca0fee26f62894aff1b676ff5b3b0df
Reviewed-on: https://go-review.googlesource.com/c/go/+/819062
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
index a6806c3..286a597 100644
--- a/src/internal/syscall/windows/syscall_windows.go
+++ b/src/internal/syscall/windows/syscall_windows.go
@@ -44,6 +44,7 @@
 	ERROR_IO_INCOMPLETE          syscall.Errno = 996
 	ERROR_NO_TOKEN               syscall.Errno = 1008
 	ERROR_NO_UNICODE_TRANSLATION syscall.Errno = 1113
+	ERROR_NONE_MAPPED            syscall.Errno = 1332
 	ERROR_CANT_ACCESS_FILE       syscall.Errno = 1920
 )
 
diff --git a/src/os/user/lookup_windows.go b/src/os/user/lookup_windows.go
index e0e77f3..1d71ca4 100644
--- a/src/os/user/lookup_windows.go
+++ b/src/os/user/lookup_windows.go
@@ -173,6 +173,9 @@
 func lookupGroupName(groupname string) (string, error) {
 	sid, _, t, e := syscall.LookupSID("", groupname)
 	if e != nil {
+		if errors.Is(e, windows.ERROR_NONE_MAPPED) {
+			return "", fmt.Errorf("%w: %w", UnknownGroupError(groupname), e)
+		}
 		return "", e
 	}
 	if !isValidGroupAccountType(t) {
@@ -454,6 +457,9 @@
 func lookupUser(username string) (*User, error) {
 	sid, _, t, e := syscall.LookupSID("", username)
 	if e != nil {
+		if errors.Is(e, windows.ERROR_NONE_MAPPED) {
+			return nil, fmt.Errorf("%w: %w", UnknownUserError(username), e)
+		}
 		return nil, e
 	}
 	if !isValidUserAccountType(sid, t) {
diff --git a/src/os/user/user_windows_test.go b/src/os/user/user_windows_test.go
index c958115..d6f25ed 100644
--- a/src/os/user/user_windows_test.go
+++ b/src/os/user/user_windows_test.go
@@ -358,3 +358,34 @@
 		}
 	}
 }
+
+func TestLookupUnknownName(t *testing.T) {
+	// A name that resolves to no account makes LookupAccountName fail
+	// with ERROR_NONE_MAPPED. That is the case Lookup and LookupGroup
+	// document as UnknownUserError and UnknownGroupError.
+	const name = "go-test-no-such-account-4a9f2c"
+
+	if _, err := Lookup(name); err == nil {
+		t.Errorf("Lookup(%q) = nil error, want UnknownUserError", name)
+	} else {
+		var unknown UnknownUserError
+		if !errors.As(err, &unknown) {
+			t.Errorf("Lookup(%q) error = %v (%T), want UnknownUserError", name, err, err)
+		}
+		if !errors.Is(err, windows.ERROR_NONE_MAPPED) {
+			t.Errorf("Lookup(%q) error = %v, want it to wrap ERROR_NONE_MAPPED", name, err)
+		}
+	}
+
+	if _, err := LookupGroup(name); err == nil {
+		t.Errorf("LookupGroup(%q) = nil error, want UnknownGroupError", name)
+	} else {
+		var unknown UnknownGroupError
+		if !errors.As(err, &unknown) {
+			t.Errorf("LookupGroup(%q) error = %v (%T), want UnknownGroupError", name, err, err)
+		}
+		if !errors.Is(err, windows.ERROR_NONE_MAPPED) {
+			t.Errorf("LookupGroup(%q) error = %v, want it to wrap ERROR_NONE_MAPPED", name, err)
+		}
+	}
+}