aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--build.bat2
-rw-r--r--golang-create-environment-block-process-creation-token.patch164
-rw-r--r--service/ipc_server.go13
-rw-r--r--service/securityapi.go29
-rw-r--r--service/service_manager.go18
-rw-r--r--service/zsyscall_windows.go33
-rw-r--r--updater/downloader.go4
-rw-r--r--updater/msirunner_windows.go3
8 files changed, 178 insertions, 88 deletions
diff --git a/build.bat b/build.bat
index bf3b0b4a..bb3d00f6 100644
--- a/build.bat
+++ b/build.bat
@@ -20,7 +20,7 @@ if exist .deps\prepared goto :render
rem Mirror of https://sourceforge.net/projects/gnuwin32/files/patch/2.5.9-7/patch-2.5.9-7-bin.zip with fixed manifest
call :download patch.zip https://download.wireguard.com/windows-toolchain/distfiles/patch-2.5.9-7-bin-fixed-manifest.zip 25977006ca9713f2662a5d0a2ed3a5a138225b8be3757035bd7da9dcf985d0a1 "--strip-components 1 bin" || goto :error
echo [+] Patching go
- .\patch.exe -f -N -r- -d go -p1 --binary < ..\golang-security-attribute-process-creation.patch || goto :error
+ for %%a in ("..\golang-*.patch") do .\patch.exe -f -N -r- -d go -p1 --binary < "%%a" || goto :error
copy /y NUL prepared > NUL || goto :error
cd .. || goto :error
diff --git a/golang-create-environment-block-process-creation-token.patch b/golang-create-environment-block-process-creation-token.patch
new file mode 100644
index 00000000..b5d75f60
--- /dev/null
+++ b/golang-create-environment-block-process-creation-token.patch
@@ -0,0 +1,164 @@
+From 3746d930467a486fb714a885ad92ebe16cf33c2a Mon Sep 17 00:00:00 2001
+From: Jason A. Donenfeld <Jason@zx2c4.com>
+Date: Sun, 12 May 2019 14:34:30 +0200
+Subject: [PATCH] os: pass correct environment when creating Windows processes
+
+This is CVE-2019-11888.
+
+Previously, passing a nil environment but a non-nil token would result
+in the new potentially unprivileged process inheriting the parent
+potentially privileged environment, or would result in the new
+potentially privileged process inheriting the parent potentially
+unprivileged environment. Either way, it's bad. In the former case, it's
+an infoleak. In the latter case, it's a possible EoP, since things like
+PATH could be overwritten.
+
+Not specifying an environment currently means, "use the existing
+environment". This commit amends the behavior to be, "use the existing
+environment of the token the process is being created for." The behavior
+therefore stays the same when creating processes without specifying a
+token. And it does the correct thing when creating processes when
+specifying a token.
+
+Change-Id: Ia57f6e89b97bdbaf7274d6a89c1d9948b6d40ef5
+---
+
+diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
+index 121132f..099e91e 100644
+--- a/src/internal/syscall/windows/syscall_windows.go
++++ b/src/internal/syscall/windows/syscall_windows.go
+@@ -305,3 +305,6 @@
+ func LoadGetFinalPathNameByHandle() error {
+ return procGetFinalPathNameByHandleW.Find()
+ }
++
++//sys CreateEnvironmentBlock(block **uint16, token syscall.Token, inheritExisting bool) (err error) = userenv.CreateEnvironmentBlock
++//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
+diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
+index 9527a37..ca5b4e6 100644
+--- a/src/internal/syscall/windows/zsyscall_windows.go
++++ b/src/internal/syscall/windows/zsyscall_windows.go
+@@ -58,6 +58,8 @@
+ procNetShareAdd = modnetapi32.NewProc("NetShareAdd")
+ procNetShareDel = modnetapi32.NewProc("NetShareDel")
+ procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
++ procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
++ procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock")
+ procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
+ procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
+ procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
+@@ -220,6 +222,36 @@
+ return
+ }
+
++func CreateEnvironmentBlock(block **uint16, token syscall.Token, inheritExisting bool) (err error) {
++ var _p0 uint32
++ if inheritExisting {
++ _p0 = 1
++ } else {
++ _p0 = 0
++ }
++ r1, _, e1 := syscall.Syscall(procCreateEnvironmentBlock.Addr(), 3, uintptr(unsafe.Pointer(block)), uintptr(token), uintptr(_p0))
++ if r1 == 0 {
++ if e1 != 0 {
++ err = errnoErr(e1)
++ } else {
++ err = syscall.EINVAL
++ }
++ }
++ return
++}
++
++func DestroyEnvironmentBlock(block *uint16) (err error) {
++ r1, _, e1 := syscall.Syscall(procDestroyEnvironmentBlock.Addr(), 1, uintptr(unsafe.Pointer(block)), 0, 0)
++ if r1 == 0 {
++ if e1 != 0 {
++ err = errnoErr(e1)
++ } else {
++ err = syscall.EINVAL
++ }
++ }
++ return
++}
++
+ func ImpersonateSelf(impersonationlevel uint32) (err error) {
+ r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(impersonationlevel), 0, 0)
+ if r1 == 0 {
+diff --git a/src/os/env_default.go b/src/os/env_default.go
+new file mode 100644
+index 0000000..91c5e9a
+--- /dev/null
++++ b/src/os/env_default.go
+@@ -0,0 +1,13 @@
++// Copyright 2019 The Go Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style
++// license that can be found in the LICENSE file.
++
++// +build !windows
++
++package os
++
++import "syscall"
++
++func environForSysProcAttr(sys *syscall.SysProcAttr) (env []string) {
++ return Environ()
++}
+diff --git a/src/os/env_windows.go b/src/os/env_windows.go
+new file mode 100644
+index 0000000..76cf011
+--- /dev/null
++++ b/src/os/env_windows.go
+@@ -0,0 +1,40 @@
++// Copyright 2019 The Go Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style
++// license that can be found in the LICENSE file.
++
++package os
++
++import (
++ "internal/syscall/windows"
++ "syscall"
++ "unicode/utf16"
++ "unsafe"
++)
++
++func environForSysProcAttr(sys *syscall.SysProcAttr) (env []string) {
++ if sys == nil || sys.Token == 0 {
++ return Environ()
++ }
++ var block *uint16
++ err := windows.CreateEnvironmentBlock(&block, sys.Token, false)
++ if err != nil {
++ return nil
++ }
++ blockp := uintptr(unsafe.Pointer(block))
++ for {
++ entry := (*[(1 << 30) - 1]uint16)(unsafe.Pointer(blockp))[:]
++ for i, v := range entry {
++ if v == 0 {
++ entry = entry[:i]
++ break
++ }
++ }
++ if len(entry) == 0 {
++ break
++ }
++ env = append(env, string(utf16.Decode(entry)))
++ blockp += 2 * (uintptr(len(entry)) + 1)
++ }
++ windows.DestroyEnvironmentBlock(block)
++ return
++}
+diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go
+index 7b1ef67..651d24d 100644
+--- a/src/os/exec_posix.go
++++ b/src/os/exec_posix.go
+@@ -38,7 +38,7 @@
+ Sys: attr.Sys,
+ }
+ if sysattr.Env == nil {
+- sysattr.Env = Environ()
++ sysattr.Env = environForSysProcAttr(sysattr.Sys)
+ }
+ sysattr.Files = make([]uintptr, 0, len(attr.Files))
+ for _, f := range attr.Files {
diff --git a/service/ipc_server.go b/service/ipc_server.go
index becad3ee..12e01ce2 100644
--- a/service/ipc_server.go
+++ b/service/ipc_server.go
@@ -29,14 +29,9 @@ var managerServicesLock sync.RWMutex
var haveQuit uint32
var quitManagersChan = make(chan struct{}, 1)
-type UserTokenInfo struct {
- elevatedToken windows.Token
- elevatedEnvironment []string
-}
-
type ManagerService struct {
events *os.File
- userTokenInfo *UserTokenInfo
+ elevatedToken windows.Token
}
func (s *ManagerService) StoredConfig(tunnelName string, config *conf.Config) error {
@@ -258,7 +253,7 @@ func (s *ManagerService) UpdateState(_ uintptr, state *UpdateState) error {
}
func (s *ManagerService) Update(_ uintptr, _ *uintptr) error {
- progress := updater.DownloadVerifyAndExecute(uintptr(s.userTokenInfo.elevatedToken), s.userTokenInfo.elevatedEnvironment)
+ progress := updater.DownloadVerifyAndExecute(uintptr(s.elevatedToken))
go func() {
for {
dp := <-progress
@@ -271,10 +266,10 @@ func (s *ManagerService) Update(_ uintptr, _ *uintptr) error {
return nil
}
-func IPCServerListen(reader *os.File, writer *os.File, events *os.File, userTokenInfo *UserTokenInfo) error {
+func IPCServerListen(reader *os.File, writer *os.File, events *os.File, elevatedToken windows.Token) error {
service := &ManagerService{
events: events,
- userTokenInfo: userTokenInfo,
+ elevatedToken: elevatedToken,
}
server := rpc.NewServer()
diff --git a/service/securityapi.go b/service/securityapi.go
index 37713b32..d89d9404 100644
--- a/service/securityapi.go
+++ b/service/securityapi.go
@@ -9,7 +9,6 @@ import (
"errors"
"golang.org/x/sys/windows"
"runtime"
- "unicode/utf16"
"unsafe"
)
@@ -53,34 +52,6 @@ const (
SE_GROUP_USE_FOR_DENY_ONLY = 0x00000010
)
-//sys createEnvironmentBlock(block *uintptr, token windows.Token, inheritExisting bool) (err error) = userenv.CreateEnvironmentBlock
-//sys destroyEnvironmentBlock(block uintptr) (err error) = userenv.DestroyEnvironmentBlock
-
-func userEnviron(token windows.Token) (env []string, err error) {
- var block uintptr
- err = createEnvironmentBlock(&block, token, false)
- if err != nil {
- return
- }
- offset := uintptr(0)
- for {
- entry := (*[(1 << 30) - 1]uint16)(unsafe.Pointer(block + offset))[:]
- for i, v := range entry {
- if v == 0 {
- entry = entry[:i]
- break
- }
- }
- if len(entry) == 0 {
- break
- }
- env = append(env, string(utf16.Decode(entry)))
- offset += 2 * (uintptr(len(entry)) + 1)
- }
- destroyEnvironmentBlock(block)
- return
-}
-
func tokenIsElevated(token windows.Token) bool {
var isElevated uint32
var outLen uint32
diff --git a/service/service_manager.go b/service/service_manager.go
index c61bd517..92508215 100644
--- a/service/service_manager.go
+++ b/service/service_manager.go
@@ -110,22 +110,17 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
userToken.Close()
return
}
- userTokenInfo := &UserTokenInfo{}
- userTokenInfo.elevatedToken, err = getElevatedToken(userToken)
+ //TODO: The environment that Go gets from CreateEnvironmentBlock seems to have the same PATH as the userToken. Aren't there attacks?
+ elevatedToken, err := getElevatedToken(userToken)
if err != nil {
log.Printf("Unable to elevate token: %v", err)
return
}
- if userTokenInfo.elevatedToken != userToken {
+ if elevatedToken != userToken {
userToken.Close()
}
- defer userTokenInfo.elevatedToken.Close()
+ defer elevatedToken.Close()
userToken = 0
- userTokenInfo.elevatedEnvironment, err = userEnviron(userTokenInfo.elevatedToken) //TODO: This seems to have the same PATH as the userToken. Aren't there attacks?
- if err != nil {
- log.Printf("Unable to determine elevated environment: %v", err)
- return
- }
first := true
for {
if stoppingManager {
@@ -155,7 +150,7 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
return
}
ourEvents, theirEvents, theirEventStr, err := inheritableEvents()
- err = IPCServerListen(ourReader, ourWriter, ourEvents, userTokenInfo)
+ err = IPCServerListen(ourReader, ourWriter, ourEvents, elevatedToken)
if err != nil {
log.Printf("Unable to listen on IPC pipes: %v", err)
return
@@ -169,10 +164,9 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
log.Printf("Starting UI process for user '%s@%s' for session %d", username, domain, session)
attr := &os.ProcAttr{
Sys: &syscall.SysProcAttr{
- Token: syscall.Token(userTokenInfo.elevatedToken),
+ Token: syscall.Token(elevatedToken),
},
Files: []*os.File{devNull, devNull, devNull},
- Env: userTokenInfo.elevatedEnvironment,
}
procsLock.Lock()
var proc *os.Process
diff --git a/service/zsyscall_windows.go b/service/zsyscall_windows.go
index 20f9753b..bb8d89d4 100644
--- a/service/zsyscall_windows.go
+++ b/service/zsyscall_windows.go
@@ -38,15 +38,12 @@ func errnoErr(e syscall.Errno) error {
var (
modwtsapi32 = windows.NewLazySystemDLL("wtsapi32.dll")
- moduserenv = windows.NewLazySystemDLL("userenv.dll")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
procWTSQueryUserToken = modwtsapi32.NewProc("WTSQueryUserToken")
procWTSEnumerateSessionsW = modwtsapi32.NewProc("WTSEnumerateSessionsW")
procWTSFreeMemory = modwtsapi32.NewProc("WTSFreeMemory")
- procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
- procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock")
procNotifyServiceStatusChangeW = modadvapi32.NewProc("NotifyServiceStatusChangeW")
procSleepEx = modkernel32.NewProc("SleepEx")
)
@@ -80,36 +77,6 @@ func wtsFreeMemory(ptr uintptr) {
return
}
-func createEnvironmentBlock(block *uintptr, token windows.Token, inheritExisting bool) (err error) {
- var _p0 uint32
- if inheritExisting {
- _p0 = 1
- } else {
- _p0 = 0
- }
- r1, _, e1 := syscall.Syscall(procCreateEnvironmentBlock.Addr(), 3, uintptr(unsafe.Pointer(block)), uintptr(token), uintptr(_p0))
- if r1 == 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
-
-func destroyEnvironmentBlock(block uintptr) (err error) {
- r1, _, e1 := syscall.Syscall(procDestroyEnvironmentBlock.Addr(), 1, uintptr(block), 0, 0)
- if r1 == 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
-
func notifyServiceStatusChange(service windows.Handle, notifyMask uint32, notifyBuffer uintptr) (status uint32) {
r0, _, _ := syscall.Syscall(procNotifyServiceStatusChangeW.Addr(), 3, uintptr(service), uintptr(notifyMask), uintptr(notifyBuffer))
status = uint32(r0)
diff --git a/updater/downloader.go b/updater/downloader.go
index 2f83b9b2..88931d61 100644
--- a/updater/downloader.go
+++ b/updater/downloader.go
@@ -70,7 +70,7 @@ func CheckForUpdate() (*UpdateFound, error) {
var updateInProgress = uint32(0)
-func DownloadVerifyAndExecute(userToken uintptr, userEnvironment []string) (progress chan DownloadProgress) {
+func DownloadVerifyAndExecute(userToken uintptr) (progress chan DownloadProgress) {
progress = make(chan DownloadProgress, 128)
progress <- DownloadProgress{Activity: "Initializing"}
@@ -158,7 +158,7 @@ func DownloadVerifyAndExecute(userToken uintptr, userEnvironment []string) (prog
}
progress <- DownloadProgress{Activity: "Installing update"}
- err = runMsi(name, userToken, userEnvironment)
+ err = runMsi(name, userToken)
os.Remove(name) //TODO: Do we have any sort of TOCTOU here?
if err != nil {
progress <- DownloadProgress{Error: err}
diff --git a/updater/msirunner_windows.go b/updater/msirunner_windows.go
index de3fb58e..1ea405f4 100644
--- a/updater/msirunner_windows.go
+++ b/updater/msirunner_windows.go
@@ -19,7 +19,7 @@ import (
"unsafe"
)
-func runMsi(msiPath string, userToken uintptr, env []string) error {
+func runMsi(msiPath string, userToken uintptr) error {
system32, err := windows.GetSystemDirectory()
if err != nil {
return err
@@ -34,7 +34,6 @@ func runMsi(msiPath string, userToken uintptr, env []string) error {
Token: syscall.Token(userToken),
},
Files: []*os.File{devNull, devNull, devNull},
- Env: env,
Dir: path.Dir(msiPath),
}
msiexec := path.Join(system32, "msiexec.exe")