windows: avoid length overflow in NewNTString

NewNTString initializes an NTString with RtlInitString, which cannot accurately represent sources whose NUL-terminated byte length exceeds MAX_USHORT. Reject strings whose byte representation cannot fit in the uint16 length fields before calling RtlInitString.

Add a boundary round-trip test for NTString.

Fixes golang/go#80103

Change-Id: Id38882a952330305e6c23d0adc86a8bf7920d30e
Reviewed-on: https://go-review.googlesource.com/c/sys/+/793100
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>
Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go
index 9755bca..e6966b4 100644
--- a/windows/syscall_windows.go
+++ b/windows/syscall_windows.go
@@ -1728,11 +1728,15 @@
 // the more common *uint16 string type.
 func NewNTString(s string) (*NTString, error) {
 	var nts NTString
-	s8, err := BytePtrFromString(s)
+	s8, err := ByteSliceFromString(s)
 	if err != nil {
 		return nil, err
 	}
-	RtlInitString(&nts, s8)
+	// The source string plus its terminating NUL must fit within MAX_USHORT.
+	if len(s8) > MAX_USHORT {
+		return nil, syscall.EINVAL
+	}
+	RtlInitString(&nts, &s8[0])
 	return &nts, nil
 }
 
diff --git a/windows/syscall_windows_test.go b/windows/syscall_windows_test.go
index 98bba20..92da6bd 100644
--- a/windows/syscall_windows_test.go
+++ b/windows/syscall_windows_test.go
@@ -1516,6 +1516,43 @@
 	}
 }
 
+func TestRoundtripNTString(t *testing.T) {
+	// NTString maximum string length must fit in a uint16, less for terminal NUL.
+	maxString := strings.Repeat("*", windows.MAX_USHORT-1)
+	for _, test := range []struct {
+		s       string
+		wantErr bool
+	}{{
+		s: "",
+	}, {
+		s: "hello",
+	}, {
+		s: maxString,
+	}, {
+		s:       maxString + "*",
+		wantErr: true,
+	}, {
+		s:       "a\x00a",
+		wantErr: true,
+	}} {
+		nts, err := windows.NewNTString(test.s)
+		if (err != nil) != test.wantErr {
+			t.Errorf("NewNTString(%q): %v, wantErr:%v", test.s, err, test.wantErr)
+			continue
+		}
+		if err != nil {
+			if !errors.Is(err, syscall.EINVAL) {
+				t.Errorf("NewNTString(%q): %v, want %v", test.s, err, syscall.EINVAL)
+			}
+			continue
+		}
+		s2 := nts.String()
+		if test.s != s2 {
+			t.Errorf("round trip of %q = %q, wanted original", test.s, s2)
+		}
+	}
+}
+
 func TestIsProcessorFeaturePresent(t *testing.T) {
 	// according to
 	// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent
diff --git a/windows/types_windows.go b/windows/types_windows.go
index d2574a7..75a50b3 100644
--- a/windows/types_windows.go
+++ b/windows/types_windows.go
@@ -169,6 +169,7 @@
 	FORMAT_MESSAGE_ARGUMENT_ARRAY  = 8192
 	FORMAT_MESSAGE_MAX_WIDTH_MASK  = 255
 
+	MAX_USHORT    = 0xffff
 	MAX_PATH      = 260
 	MAX_LONG_PATH = 32768