Skip to content

Commit f490951

Browse files
committed
conf: move configuration to C:\ProgramData\WireGuard
Still looking into the security implications of this. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
1 parent 3e7d06a commit f490951

File tree

7 files changed

+120
-54
lines changed

7 files changed

+120
-54
lines changed

attacksurface.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Wintun is a kernel driver. It exposes:
1616

1717
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:
1818

19+
- The default dacl/owner/group is set to `O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FR;;;BA)`.
1920
- 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".
2021
- 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.
2122
- It handles data from its two UDP sockets, accessible to the public Internet.
@@ -26,10 +27,11 @@ The tunnel service is a userspace service running as Local System, responsible f
2627

2728
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:
2829

30+
- The default dacl/owner/group is set to `O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FR;;;BA)`.
2931
- Extensive IPC using unnamed pipes, inherited by the UI process.
3032
- A readable `CreateFileMapping` handle to a binary ringlog shared by all services, inherited by the UI process.
3133
- It listens for service changes in tunnel services according to the string prefix "WireGuardTunnel$".
32-
- It manages DPAPI-encrypted configuration files in Local System's local appdata directory, and makes some effort to enforce good configuration filenames.
34+
- It manages DPAPI-encrypted configuration files in `C:\ProgramData\WireGuard` and makes some effort to enforce good configuration filenames.
3335
- 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.
3436

3537
### UI

conf/migration_windows.go

+31-25
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,52 @@
66
package conf
77

88
import (
9+
"io/ioutil"
910
"log"
11+
"os"
1012
"path/filepath"
11-
"strings"
1213

1314
"golang.org/x/sys/windows"
1415
)
1516

16-
func maybeMigrate(c string) {
17+
func maybeMigrateConfiguration(c string) {
1718
if disableAutoMigration {
1819
return
1920
}
20-
21-
vol := filepath.VolumeName(c)
22-
withoutVol := strings.TrimPrefix(c, vol)
23-
oldRoot := filepath.Join(vol, "\\windows.old")
24-
oldC := filepath.Join(oldRoot, withoutVol)
25-
26-
sd, err := windows.GetNamedSecurityInfo(oldRoot, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION)
27-
if err == windows.ERROR_PATH_NOT_FOUND || err == windows.ERROR_FILE_NOT_FOUND {
28-
return
29-
}
21+
oldRoot, err := windows.KnownFolderPath(windows.FOLDERID_LocalAppData, windows.KF_FLAG_DEFAULT)
3022
if err != nil {
31-
log.Printf("Not migrating configuration from ‘%s’ due to GetNamedSecurityInfo error: %v", oldRoot, err)
3223
return
3324
}
34-
owner, defaulted, err := sd.Owner()
25+
oldC := filepath.Join(oldRoot, "WireGuard", "Configurations")
26+
files, err := ioutil.ReadDir(oldC)
3527
if err != nil {
36-
log.Printf("Not migrating configuration from ‘%s’ due to GetSecurityDescriptorOwner error: %v", oldRoot, err)
37-
return
38-
}
39-
if defaulted || (!owner.IsWellKnown(windows.WinLocalSystemSid) && !owner.IsWellKnown(windows.WinBuiltinAdministratorsSid)) {
40-
log.Printf("Not migrating configuration from ‘%s’, as it is not explicitly owned by SYSTEM or Built-in Administrators, but rather ‘%v’", oldRoot, owner)
4128
return
4229
}
43-
err = windows.MoveFileEx(windows.StringToUTF16Ptr(oldC), windows.StringToUTF16Ptr(c), windows.MOVEFILE_COPY_ALLOWED)
44-
if err != nil {
45-
if err != windows.ERROR_FILE_NOT_FOUND && err != windows.ERROR_ALREADY_EXISTS {
46-
log.Printf("Not migrating configuration from ‘%s’ due to error when moving files: %v", oldRoot, err)
30+
for i := range files {
31+
if files[i].IsDir() {
32+
continue
4733
}
48-
return
34+
newPath := filepath.Join(c, files[i].Name())
35+
newFile, err := os.OpenFile(newPath, os.O_EXCL | os.O_CREATE | os.O_WRONLY, 0600)
36+
if err != nil {
37+
continue
38+
}
39+
oldPath := filepath.Join(oldC, files[i].Name())
40+
oldConfig, err := ioutil.ReadFile(oldPath)
41+
if err != nil {
42+
newFile.Close()
43+
os.Remove(newPath)
44+
continue
45+
}
46+
_, err = newFile.Write(oldConfig)
47+
if err != nil {
48+
newFile.Close()
49+
os.Remove(newPath)
50+
continue
51+
}
52+
newFile.Close()
53+
os.Remove(oldPath)
54+
log.Printf("Migrated configuration from ‘%s’ to ‘%s’", oldPath, newPath)
4955
}
50-
log.Printf("Migrated configuration from ‘%s’", oldRoot)
56+
os.Remove(oldC)
5157
}

conf/path_windows.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"path/filepath"
1111

1212
"golang.org/x/sys/windows"
13+
14+
"golang.zx2c4.com/wireguard/windows/elevate"
1315
)
1416

1517
var cachedConfigFileDir string
@@ -25,7 +27,7 @@ func tunnelConfigurationsDirectory() (string, error) {
2527
return "", err
2628
}
2729
c := filepath.Join(root, "Configurations")
28-
maybeMigrate(c)
30+
maybeMigrateConfiguration(c)
2931
err = os.MkdirAll(c, os.ModeDir|0700)
3032
if err != nil {
3133
return "", err
@@ -46,12 +48,23 @@ func RootDirectory() (string, error) {
4648
if cachedRootDir != "" {
4749
return cachedRootDir, nil
4850
}
49-
root, err := windows.KnownFolderPath(windows.FOLDERID_LocalAppData, windows.KF_FLAG_CREATE)
51+
root, err := windows.KnownFolderPath(windows.FOLDERID_ProgramData, windows.KF_FLAG_CREATE)
5052
if err != nil {
5153
return "", err
5254
}
5355
c := filepath.Join(root, "WireGuard")
54-
err = os.MkdirAll(c, os.ModeDir|0700)
56+
err = os.Mkdir(c, 0600)
57+
if err != nil && !os.IsExist(err) {
58+
return "", err
59+
}
60+
61+
owner, group, dacl, err := elevate.GetDefaultObjectDacl()
62+
if err != nil {
63+
return "", err
64+
}
65+
//TODO: what about clearing preexisting SACL?
66+
//TODO: symlink mischief?
67+
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)
5568
if err != nil {
5669
return "", err
5770
}

elevate/privileges.go

+47
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,50 @@ func DropAllPrivileges(retainDriverLoading bool) error {
5454
runtime.KeepAlive(buffer)
5555
return err
5656
}
57+
58+
func GetDefaultObjectDacl() (owner, group *windows.SID, dacl *windows.ACL, err error) {
59+
sd, err := windows.SecurityDescriptorFromString("O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FR;;;BA)")
60+
if err != nil {
61+
return nil, nil, nil, err
62+
}
63+
owner, _, err = sd.Owner()
64+
if err != nil {
65+
return nil, nil, nil, err
66+
}
67+
group, _, err = sd.Group()
68+
if err != nil {
69+
return nil, nil, nil, err
70+
}
71+
dacl, _, err = sd.DACL()
72+
if err != nil {
73+
return nil, nil, nil, err
74+
}
75+
return
76+
}
77+
78+
func SetDefaultObjectDacl() error {
79+
owner, group, dacl, err := GetDefaultObjectDacl()
80+
if err != nil {
81+
return err
82+
}
83+
var token windows.Token
84+
err = windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_ADJUST_DEFAULT, &token)
85+
if err != nil {
86+
return err
87+
}
88+
defer token.Close()
89+
err = windows.SetTokenInformation(token, windows.TokenOwner, (*byte)(unsafe.Pointer(&owner)), uint32(unsafe.Sizeof(uintptr(0))))
90+
if err != nil {
91+
return err
92+
}
93+
err = windows.SetTokenInformation(token, windows.TokenPrimaryGroup, (*byte)(unsafe.Pointer(&group)), uint32(unsafe.Sizeof(uintptr(0))))
94+
if err != nil {
95+
return err
96+
}
97+
err = windows.SetTokenInformation(token, windows.TokenDefaultDacl, (*byte)(unsafe.Pointer(&dacl)), uint32(unsafe.Sizeof(uintptr(0))))
98+
if err != nil {
99+
return err
100+
}
101+
//TODO: sacl?
102+
return nil
103+
}

installer/customactions.c

+9-8
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ __declspec(dllexport) UINT __stdcall EvaluateWireGuardComponents(MSIHANDLE insta
305305
__declspec(dllexport) UINT __stdcall RemoveConfigFolder(MSIHANDLE installer)
306306
{
307307
LSTATUS ret;
308-
TCHAR path[MAX_PATH];
308+
TCHAR path[MAX_PATH], *program_data;
309309
DWORD path_len = _countof(path);
310310
bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
311311

@@ -316,17 +316,18 @@ __declspec(dllexport) UINT __stdcall RemoveConfigFolder(MSIHANDLE installer)
316316
}
317317
if (_tcscmp(path, _T("remove")))
318318
goto out;
319-
ret = SHRegGetPath(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\S-1-5-18"),
320-
TEXT("ProfileImagePath"), path, 0);
321-
if (ret != ERROR_SUCCESS) {
322-
log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("SHRegGetPath failed"));
319+
ret = SHGetKnownFolderPath(&FOLDERID_ProgramData, KF_FLAG_DEFAULT, NULL, &program_data);
320+
if (ret != S_OK) {
321+
log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("SHGetKnownFolderPath(FOLDERID_ProgramData) failed"));
323322
goto out;
324323
}
325-
if (!PathAppend(path, TEXT("AppData\\Local\\WireGuard"))) {
326-
log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathAppend(\"%1\", \"AppData\\Local\\WireGuard\") failed"), path);
327-
goto out;
324+
if (!PathCombine(path, program_data, TEXT("WireGuard"))) {
325+
log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathCombine(\"%1\", \"WireGuard\") failed"), path);
326+
goto out_free;
328327
}
329328
remove_directory_recursive(installer, path, 10);
329+
out_free:
330+
CoTaskMemFree(program_data);
330331
out:
331332
if (is_com_initialized)
332333
CoUninitialize();

main.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,11 @@ func main() {
203203
if len(os.Args) != 2 {
204204
usage()
205205
}
206-
err := manager.Run()
206+
err := elevate.SetDefaultObjectDacl()
207+
if err != nil {
208+
fatal(err)
209+
}
210+
err = manager.Run()
207211
if err != nil {
208212
fatal(err)
209213
}
@@ -230,7 +234,11 @@ func main() {
230234
if len(os.Args) != 3 {
231235
usage()
232236
}
233-
err := tunnel.Run(os.Args[2])
237+
err := elevate.SetDefaultObjectDacl()
238+
if err != nil {
239+
fatal(err)
240+
}
241+
err = tunnel.Run(os.Args[2])
234242
if err != nil {
235243
fatal(err)
236244
}

ringlogger/dump.go

+4-15
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,24 @@ import (
1111
"path/filepath"
1212

1313
"golang.org/x/sys/windows"
14-
"golang.org/x/sys/windows/registry"
1514

1615
"golang.zx2c4.com/wireguard/windows/conf"
1716
)
1817

19-
func DumpTo(out io.Writer, localSystem bool) error {
18+
func DumpTo(out io.Writer, notSystem bool) error {
2019
var path string
21-
if !localSystem {
20+
if !notSystem {
2221
root, err := conf.RootDirectory()
2322
if err != nil {
2423
return err
2524
}
2625
path = filepath.Join(root, "log.bin")
2726
} else {
28-
k, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\S-1-5-18", registry.QUERY_VALUE)
27+
root, err := windows.KnownFolderPath(windows.FOLDERID_ProgramData, windows.KF_FLAG_DEFAULT)
2928
if err != nil {
3029
return err
3130
}
32-
defer k.Close()
33-
34-
systemprofile, _, err := k.GetStringValue("ProfileImagePath")
35-
if err != nil {
36-
return err
37-
}
38-
systemprofile, err = registry.ExpandString(systemprofile)
39-
if err != nil {
40-
return err
41-
}
42-
path = filepath.Join(systemprofile, "AppData", "Local", "WireGuard", "log.bin")
31+
path = filepath.Join(root, "WireGuard", "log.bin")
4332
}
4433
file, err := os.Open(path)
4534
if err != nil {

0 commit comments

Comments
 (0)