Skip to content

x/sys/windows: NTUnicodeString of PROCESS_BASIC_INFORMATION.PebBaseAddress.ProcessParameters.CommandLine is incorrectly converted to slice #73460

Closed
@xformerfhs

Description

@xformerfhs

Go version

go version go1.24.2 windows/amd64

Output of go env in your module/workspace:

set AR=ar
set CC=gcc
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_ENABLED=0
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set CXX=g++
set GCCGO=gccgo
set GO111MODULE=
set GOAMD64=v3
set GOARCH=amd64
set GOAUTH=netrc
set GOBIN=
set GOCACHE=C:\Users\User\AppData\Local\go-build
set GOCACHEPROG=
set GODEBUG=
set GOENV=C:\Users\User\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFIPS140=off
set GOFLAGS=
set GOGCCFLAGS=-m64 -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=T:\UserTemp\User\go-build1636068885=/tmp/go-build -gno-record-gcc-switches
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMOD=D:\Users\User\go\pkg\mod\golang.org\x\sys@v0.32.0\go.mod
set GOMODCACHE=D:\Users\User\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=D:\Users\User\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTELEMETRY=on
set GOTELEMETRYDIR=C:\Users\User\AppData\Roaming\go\telemetry
set GOTMPDIR=T:\UserTemp\User
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.24.2
set GOWORK=
set PKG_CONFIG=pkg-config

What did you do?

I wanted to write a function that wipes the command line in the Windows Process Environment Block so that sensible information like password or key parameters do not show up when processes are listed.

The package golang.org/x/sys/windows has all that is needed to accomplish this.

Getting at the command line in the PEB is easily done with this code:

var (
	ntDLL                   = windows.MustLoadDLL(`ntdll.dll`)
	queryInformationProcess = ntDLL.MustFindProc(`NtQueryInformationProcess`)
)

func WipeCommandLine() error {
	hProcess, err := windows.OpenProcess(
		windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ,
		false,
		windows.GetCurrentProcessId(),
	)
	if err != nil {
		return err
	}

	var pbi windows.PROCESS_BASIC_INFORMATION
	var status uintptr
	status, _, _ = queryInformationProcess.Call(
		uintptr(hProcess),
		windows.ProcessBasicInformation,
		uintptr(unsafe.Pointer(&pbi)),
		unsafe.Sizeof(pbi),
		uintptr(0),
	)
	if status != 0 {
		return fmt.Errorf(`NtQueryInformationProcess failed with NT status 0x%08X`, status)
	}

	commandLine := pbi.PebBaseAddress.ProcessParameters.CommandLine
}

So far, so good. CommandLine is a NTUnicodeString structure. It has the fields Buffer, Length and MaximumLength and the convenience functions Slice and String to convert the Buffer into something useable by Go.

However, there is a caveat: As documented in Microsoft Learn the fields Length and MaximumLength count the lengths in units of bytes, not wchar_t (uint16)!

To get the character count one needs to halve these lengths.

The function *NTUnicodeString.Slice() in golang.org\x\sys@v0.32.0\windows has this code:

// Slice returns a uint16 slice that aliases the data in the NTUnicodeString.
func (s *NTUnicodeString) Slice() []uint16 {
	slice := unsafe.Slice(s.Buffer, s.MaximumLength)
	return slice[:s.Length]
}

I.e., it treats the lengths in units of uint16!

What did you see happen?

When I convert the command line buffer to a slice with *NTUnicodeString.Slice() it has a length that is double the size of the Buffer and it contains the content of the *NTUnicodeString.Buffer twice.

Changing the elements of this slice in the second half does not have any influence on the real PEB command line.
Changing the elements of the first half of this slice changes the real PEB command line.

What did you expect to see?

The slice returned by *NTUnicodeString.Slice() (and correspondingly the string returned by *NTUnicodeString.String()) should have a length that is half the value of *NTUnicodeString.Length and a capacity that is half the value of *NTUnicodeString.MaximumLength. It should also contain only one copy of the command line and changes to it should immediately be visible in the PEB of the process.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.compiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions