Skip to content

Commit

Permalink
conf: move configuration to C:\ProgramData\WireGuard
Browse files Browse the repository at this point in the history
Still looking into the security implications of this.

Signed-off-by: Jason A. Donenfeld <[email protected]>
  • Loading branch information
zx2c4 committed Nov 10, 2020
1 parent 3e7d06a commit f490951
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 54 deletions.
4 changes: 3 additions & 1 deletion attacksurface.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Wintun is a kernel driver. It exposes:

The tunnel service is a userspace service running as Local System, responsible for creating UDP sockets, creating Wintun adapters, and speaking the WireGuard protocol between the two. It exposes:

- The default dacl/owner/group is set to `O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FR;;;BA)`.
- A listening pipe in `\\.\pipe\ProtectedPrefix\Administrators\WireGuard\%s`, where `%s` is some basename of an already valid filename. Its DACL is set to `O:SYD:(A;;GA;;;SY)`. If the config file used by the tunnel service is not DPAPI-encrypted and it is owned by a SID other than "Local System" then an additional ACE is added giving that file owner SID access to the named pipe. This pipe gives access to private keys and allows for reconfiguration of the interface, as well as rebinding to different ports (below 1024, even). Clients who connect to the pipe run `GetSecurityInfo` to verify that it is owned by "Local System".
- A global mutex is used for Wintun interface creation, with the same DACL as the pipe, but first CreatePrivateNamespace is called with a "Local System" SID.
- It handles data from its two UDP sockets, accessible to the public Internet.
Expand All @@ -26,10 +27,11 @@ The tunnel service is a userspace service running as Local System, responsible f

The manager service is a userspace service running as Local System, responsible for starting and stopping tunnel services, and ensuring a UI program with certain handles is available to Administrators. It exposes:

- The default dacl/owner/group is set to `O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FR;;;BA)`.
- Extensive IPC using unnamed pipes, inherited by the UI process.
- A readable `CreateFileMapping` handle to a binary ringlog shared by all services, inherited by the UI process.
- It listens for service changes in tunnel services according to the string prefix "WireGuardTunnel$".
- It manages DPAPI-encrypted configuration files in Local System's local appdata directory, and makes some effort to enforce good configuration filenames.
- It manages DPAPI-encrypted configuration files in `C:\ProgramData\WireGuard` and makes some effort to enforce good configuration filenames.
- It uses `WTSEnumerateSessions` and `WTSSESSION_NOTIFICATION` to walk through each available session. It then uses `WTSQueryUserToken`, and then calls `GetTokenInformation(TokenGroups)` on it. If one of the returned group's SIDs matches `IsWellKnownSid(WinBuiltinAdministratorsSid)`, and has attributes of either `SE_GROUP_ENABLED` or `SE_GROUP_USE_FOR_DENY_ONLY` and calling `GetTokenInformation(TokenElevation)` on it or its `TokenLinkedToken` indicates that either is elevated, then it spawns the UI process as that the elevated user token, passing it three unnamed pipe handles for IPC and the log mapping handle, as described above.

### UI
Expand Down
56 changes: 31 additions & 25 deletions conf/migration_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,52 @@
package conf

import (
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"

"golang.org/x/sys/windows"
)

func maybeMigrate(c string) {
func maybeMigrateConfiguration(c string) {
if disableAutoMigration {
return
}

vol := filepath.VolumeName(c)
withoutVol := strings.TrimPrefix(c, vol)
oldRoot := filepath.Join(vol, "\\windows.old")
oldC := filepath.Join(oldRoot, withoutVol)

sd, err := windows.GetNamedSecurityInfo(oldRoot, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION)
if err == windows.ERROR_PATH_NOT_FOUND || err == windows.ERROR_FILE_NOT_FOUND {
return
}
oldRoot, err := windows.KnownFolderPath(windows.FOLDERID_LocalAppData, windows.KF_FLAG_DEFAULT)
if err != nil {
log.Printf("Not migrating configuration from ‘%s’ due to GetNamedSecurityInfo error: %v", oldRoot, err)
return
}
owner, defaulted, err := sd.Owner()
oldC := filepath.Join(oldRoot, "WireGuard", "Configurations")
files, err := ioutil.ReadDir(oldC)
if err != nil {
log.Printf("Not migrating configuration from ‘%s’ due to GetSecurityDescriptorOwner error: %v", oldRoot, err)
return
}
if defaulted || (!owner.IsWellKnown(windows.WinLocalSystemSid) && !owner.IsWellKnown(windows.WinBuiltinAdministratorsSid)) {
log.Printf("Not migrating configuration from ‘%s’, as it is not explicitly owned by SYSTEM or Built-in Administrators, but rather ‘%v’", oldRoot, owner)
return
}
err = windows.MoveFileEx(windows.StringToUTF16Ptr(oldC), windows.StringToUTF16Ptr(c), windows.MOVEFILE_COPY_ALLOWED)
if err != nil {
if err != windows.ERROR_FILE_NOT_FOUND && err != windows.ERROR_ALREADY_EXISTS {
log.Printf("Not migrating configuration from ‘%s’ due to error when moving files: %v", oldRoot, err)
for i := range files {
if files[i].IsDir() {
continue
}
return
newPath := filepath.Join(c, files[i].Name())
newFile, err := os.OpenFile(newPath, os.O_EXCL | os.O_CREATE | os.O_WRONLY, 0600)
if err != nil {
continue
}
oldPath := filepath.Join(oldC, files[i].Name())
oldConfig, err := ioutil.ReadFile(oldPath)
if err != nil {
newFile.Close()
os.Remove(newPath)
continue
}
_, err = newFile.Write(oldConfig)
if err != nil {
newFile.Close()
os.Remove(newPath)
continue
}
newFile.Close()
os.Remove(oldPath)
log.Printf("Migrated configuration from ‘%s’ to ‘%s’", oldPath, newPath)
}
log.Printf("Migrated configuration from ‘%s’", oldRoot)
os.Remove(oldC)
}
19 changes: 16 additions & 3 deletions conf/path_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"path/filepath"

"golang.org/x/sys/windows"

"golang.zx2c4.com/wireguard/windows/elevate"
)

var cachedConfigFileDir string
Expand All @@ -25,7 +27,7 @@ func tunnelConfigurationsDirectory() (string, error) {
return "", err
}
c := filepath.Join(root, "Configurations")
maybeMigrate(c)
maybeMigrateConfiguration(c)
err = os.MkdirAll(c, os.ModeDir|0700)
if err != nil {
return "", err
Expand All @@ -46,12 +48,23 @@ func RootDirectory() (string, error) {
if cachedRootDir != "" {
return cachedRootDir, nil
}
root, err := windows.KnownFolderPath(windows.FOLDERID_LocalAppData, windows.KF_FLAG_CREATE)
root, err := windows.KnownFolderPath(windows.FOLDERID_ProgramData, windows.KF_FLAG_CREATE)
if err != nil {
return "", err
}
c := filepath.Join(root, "WireGuard")
err = os.MkdirAll(c, os.ModeDir|0700)
err = os.Mkdir(c, 0600)
if err != nil && !os.IsExist(err) {
return "", err
}

owner, group, dacl, err := elevate.GetDefaultObjectDacl()
if err != nil {
return "", err
}
//TODO: what about clearing preexisting SACL?
//TODO: symlink mischief?
err = windows.SetNamedSecurityInfo(c, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION | windows.GROUP_SECURITY_INFORMATION | windows.DACL_SECURITY_INFORMATION | windows.PROTECTED_DACL_SECURITY_INFORMATION | windows.ATTRIBUTE_SECURITY_INFORMATION, owner, group, dacl, nil)
if err != nil {
return "", err
}
Expand Down
47 changes: 47 additions & 0 deletions elevate/privileges.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,50 @@ func DropAllPrivileges(retainDriverLoading bool) error {
runtime.KeepAlive(buffer)
return err
}

func GetDefaultObjectDacl() (owner, group *windows.SID, dacl *windows.ACL, err error) {
sd, err := windows.SecurityDescriptorFromString("O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FR;;;BA)")
if err != nil {
return nil, nil, nil, err
}
owner, _, err = sd.Owner()
if err != nil {
return nil, nil, nil, err
}
group, _, err = sd.Group()
if err != nil {
return nil, nil, nil, err
}
dacl, _, err = sd.DACL()
if err != nil {
return nil, nil, nil, err
}
return
}

func SetDefaultObjectDacl() error {
owner, group, dacl, err := GetDefaultObjectDacl()
if err != nil {
return err
}
var token windows.Token
err = windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_ADJUST_DEFAULT, &token)
if err != nil {
return err
}
defer token.Close()
err = windows.SetTokenInformation(token, windows.TokenOwner, (*byte)(unsafe.Pointer(&owner)), uint32(unsafe.Sizeof(uintptr(0))))
if err != nil {
return err
}
err = windows.SetTokenInformation(token, windows.TokenPrimaryGroup, (*byte)(unsafe.Pointer(&group)), uint32(unsafe.Sizeof(uintptr(0))))
if err != nil {
return err
}
err = windows.SetTokenInformation(token, windows.TokenDefaultDacl, (*byte)(unsafe.Pointer(&dacl)), uint32(unsafe.Sizeof(uintptr(0))))
if err != nil {
return err
}
//TODO: sacl?
return nil
}
17 changes: 9 additions & 8 deletions installer/customactions.c
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ __declspec(dllexport) UINT __stdcall EvaluateWireGuardComponents(MSIHANDLE insta
__declspec(dllexport) UINT __stdcall RemoveConfigFolder(MSIHANDLE installer)
{
LSTATUS ret;
TCHAR path[MAX_PATH];
TCHAR path[MAX_PATH], *program_data;
DWORD path_len = _countof(path);
bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));

Expand All @@ -316,17 +316,18 @@ __declspec(dllexport) UINT __stdcall RemoveConfigFolder(MSIHANDLE installer)
}
if (_tcscmp(path, _T("remove")))
goto out;
ret = SHRegGetPath(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\S-1-5-18"),
TEXT("ProfileImagePath"), path, 0);
if (ret != ERROR_SUCCESS) {
log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("SHRegGetPath failed"));
ret = SHGetKnownFolderPath(&FOLDERID_ProgramData, KF_FLAG_DEFAULT, NULL, &program_data);
if (ret != S_OK) {
log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("SHGetKnownFolderPath(FOLDERID_ProgramData) failed"));
goto out;
}
if (!PathAppend(path, TEXT("AppData\\Local\\WireGuard"))) {
log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathAppend(\"%1\", \"AppData\\Local\\WireGuard\") failed"), path);
goto out;
if (!PathCombine(path, program_data, TEXT("WireGuard"))) {
log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathCombine(\"%1\", \"WireGuard\") failed"), path);
goto out_free;
}
remove_directory_recursive(installer, path, 10);
out_free:
CoTaskMemFree(program_data);
out:
if (is_com_initialized)
CoUninitialize();
Expand Down
12 changes: 10 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,11 @@ func main() {
if len(os.Args) != 2 {
usage()
}
err := manager.Run()
err := elevate.SetDefaultObjectDacl()
if err != nil {
fatal(err)
}
err = manager.Run()
if err != nil {
fatal(err)
}
Expand All @@ -230,7 +234,11 @@ func main() {
if len(os.Args) != 3 {
usage()
}
err := tunnel.Run(os.Args[2])
err := elevate.SetDefaultObjectDacl()
if err != nil {
fatal(err)
}
err = tunnel.Run(os.Args[2])
if err != nil {
fatal(err)
}
Expand Down
19 changes: 4 additions & 15 deletions ringlogger/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,24 @@ import (
"path/filepath"

"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"

"golang.zx2c4.com/wireguard/windows/conf"
)

func DumpTo(out io.Writer, localSystem bool) error {
func DumpTo(out io.Writer, notSystem bool) error {
var path string
if !localSystem {
if !notSystem {
root, err := conf.RootDirectory()
if err != nil {
return err
}
path = filepath.Join(root, "log.bin")
} else {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\S-1-5-18", registry.QUERY_VALUE)
root, err := windows.KnownFolderPath(windows.FOLDERID_ProgramData, windows.KF_FLAG_DEFAULT)
if err != nil {
return err
}
defer k.Close()

systemprofile, _, err := k.GetStringValue("ProfileImagePath")
if err != nil {
return err
}
systemprofile, err = registry.ExpandString(systemprofile)
if err != nil {
return err
}
path = filepath.Join(systemprofile, "AppData", "Local", "WireGuard", "log.bin")
path = filepath.Join(root, "WireGuard", "log.bin")
}
file, err := os.Open(path)
if err != nil {
Expand Down

0 comments on commit f490951

Please sign in to comment.