1// +build windows
2
3package windows
4
5import (
6	"bytes"
7	"encoding/binary"
8	"encoding/json"
9	"fmt"
10	"runtime"
11	"strings"
12	"sync"
13	"syscall"
14
15	"github.com/pkg/errors"
16	"golang.org/x/sys/windows"
17)
18
19// Cache of privilege names to LUIDs.
20var (
21	privNames     = make(map[string]int64)
22	privNameMutex sync.Mutex
23)
24
25const (
26	// SeDebugPrivilege is the name of the privilege used to debug programs.
27	SeDebugPrivilege = "SeDebugPrivilege"
28)
29
30// Errors returned by AdjustTokenPrivileges.
31const (
32	ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
33)
34
35// Attribute bits for privileges.
36const (
37	_SE_PRIVILEGE_ENABLED_BY_DEFAULT uint32 = 0x00000001
38	_SE_PRIVILEGE_ENABLED            uint32 = 0x00000002
39	_SE_PRIVILEGE_REMOVED            uint32 = 0x00000004
40	_SE_PRIVILEGE_USED_FOR_ACCESS    uint32 = 0x80000000
41)
42
43// Privilege contains information about a single privilege associated with a
44// Token.
45type Privilege struct {
46	LUID             int64  `json:"-"` // Locally unique identifier (guaranteed only until the system is restarted).
47	Name             string `json:"-"`
48	EnabledByDefault bool   `json:"enabled_by_default,omitempty"`
49	Enabled          bool   `json:"enabled"`
50	Removed          bool   `json:"removed,omitempty"`
51	Used             bool   `json:"used,omitempty"`
52}
53
54func (p Privilege) String() string {
55	var buf bytes.Buffer
56	buf.WriteString(p.Name)
57	buf.WriteString("=(")
58
59	opts := make([]string, 0, 4)
60	if p.EnabledByDefault {
61		opts = append(opts, "Default")
62	}
63	if p.Enabled {
64		opts = append(opts, "Enabled")
65	}
66	if !p.EnabledByDefault && !p.Enabled {
67		opts = append(opts, "Disabled")
68	}
69	if p.Removed {
70		opts = append(opts, "Removed")
71	}
72	if p.Used {
73		opts = append(opts, "Used")
74	}
75
76	buf.WriteString(strings.Join(opts, ", "))
77	buf.WriteString(")")
78
79	// Example: SeDebugPrivilege=(Default, Enabled)
80	return buf.String()
81}
82
83// User represent the information about a Windows account.
84type User struct {
85	SID     string
86	Account string
87	Domain  string
88	Type    uint32
89}
90
91func (u User) String() string {
92	return fmt.Sprintf(`User:%v\%v, SID:%v, Type:%v`, u.Domain, u.Account, u.SID, u.Type)
93}
94
95// DebugInfo contains general debug info about the current process.
96type DebugInfo struct {
97	OSVersion    Version              // OS version info.
98	Arch         string               // Architecture of the machine.
99	NumCPU       int                  // Number of CPUs.
100	User         User                 // User that this process is running as.
101	ProcessPrivs map[string]Privilege // Privileges held by the process.
102}
103
104func (d DebugInfo) String() string {
105	bytes, _ := json.Marshal(d)
106	return string(bytes)
107}
108
109// LookupPrivilegeName looks up a privilege name given a LUID value.
110func LookupPrivilegeName(systemName string, luid int64) (string, error) {
111	buf := make([]uint16, 256)
112	bufSize := uint32(len(buf))
113	err := _LookupPrivilegeName(systemName, &luid, &buf[0], &bufSize)
114	if err != nil {
115		return "", errors.Wrapf(err, "LookupPrivilegeName failed for luid=%v", luid)
116	}
117
118	return syscall.UTF16ToString(buf), nil
119}
120
121// mapPrivileges maps privilege names to LUID values.
122func mapPrivileges(names []string) ([]int64, error) {
123	var privileges []int64
124	privNameMutex.Lock()
125	defer privNameMutex.Unlock()
126	for _, name := range names {
127		p, ok := privNames[name]
128		if !ok {
129			err := _LookupPrivilegeValue("", name, &p)
130			if err != nil {
131				return nil, errors.Wrapf(err, "LookupPrivilegeValue failed on '%v'", name)
132			}
133			privNames[name] = p
134		}
135		privileges = append(privileges, p)
136	}
137	return privileges, nil
138}
139
140// EnableTokenPrivileges enables the specified privileges in the given
141// Token. The token must have TOKEN_ADJUST_PRIVILEGES access. If the token
142// does not already contain the privilege it cannot be enabled.
143func EnableTokenPrivileges(token syscall.Token, privileges ...string) error {
144	privValues, err := mapPrivileges(privileges)
145	if err != nil {
146		return err
147	}
148
149	var b bytes.Buffer
150	binary.Write(&b, binary.LittleEndian, uint32(len(privValues)))
151	for _, p := range privValues {
152		binary.Write(&b, binary.LittleEndian, p)
153		binary.Write(&b, binary.LittleEndian, uint32(_SE_PRIVILEGE_ENABLED))
154	}
155
156	success, err := _AdjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(b.Len()), nil, nil)
157	if !success {
158		return err
159	}
160	if err == ERROR_NOT_ALL_ASSIGNED {
161		return errors.Wrap(err, "error not all privileges were assigned")
162	}
163
164	return nil
165}
166
167// GetTokenPrivileges returns a list of privileges associated with a token.
168// The provided token must have at a minimum TOKEN_QUERY access. This is a
169// wrapper around the GetTokenInformation function.
170// https://msdn.microsoft.com/en-us/library/windows/desktop/aa446671(v=vs.85).aspx
171func GetTokenPrivileges(token syscall.Token) (map[string]Privilege, error) {
172	// Determine the required buffer size.
173	var size uint32
174	syscall.GetTokenInformation(token, syscall.TokenPrivileges, nil, 0, &size)
175
176	// This buffer will receive a TOKEN_PRIVILEGE structure.
177	b := bytes.NewBuffer(make([]byte, size))
178	err := syscall.GetTokenInformation(token, syscall.TokenPrivileges, &b.Bytes()[0], uint32(b.Len()), &size)
179	if err != nil {
180		return nil, errors.Wrap(err, "GetTokenInformation failed")
181	}
182
183	var privilegeCount uint32
184	err = binary.Read(b, binary.LittleEndian, &privilegeCount)
185	if err != nil {
186		return nil, errors.Wrap(err, "failed to read PrivilegeCount")
187	}
188
189	rtn := make(map[string]Privilege, privilegeCount)
190	for i := 0; i < int(privilegeCount); i++ {
191		var luid int64
192		err = binary.Read(b, binary.LittleEndian, &luid)
193		if err != nil {
194			return nil, errors.Wrap(err, "failed to read LUID value")
195		}
196
197		var attributes uint32
198		err = binary.Read(b, binary.LittleEndian, &attributes)
199		if err != nil {
200			return nil, errors.Wrap(err, "failed to read attributes")
201		}
202
203		name, err := LookupPrivilegeName("", luid)
204		if err != nil {
205			return nil, errors.Wrapf(err, "LookupPrivilegeName failed for LUID=%v", luid)
206		}
207
208		rtn[name] = Privilege{
209			LUID:             luid,
210			Name:             name,
211			EnabledByDefault: (attributes & _SE_PRIVILEGE_ENABLED_BY_DEFAULT) > 0,
212			Enabled:          (attributes & _SE_PRIVILEGE_ENABLED) > 0,
213			Removed:          (attributes & _SE_PRIVILEGE_REMOVED) > 0,
214			Used:             (attributes & _SE_PRIVILEGE_USED_FOR_ACCESS) > 0,
215		}
216	}
217
218	return rtn, nil
219}
220
221// GetTokenUser returns the User associated with the given Token.
222func GetTokenUser(token syscall.Token) (User, error) {
223	tokenUser, err := token.GetTokenUser()
224	if err != nil {
225		return User{}, errors.Wrap(err, "GetTokenUser failed")
226	}
227
228	var user User
229	user.SID, err = tokenUser.User.Sid.String()
230	if err != nil {
231		return user, errors.Wrap(err, "ConvertSidToStringSid failed")
232	}
233
234	user.Account, user.Domain, user.Type, err = tokenUser.User.Sid.LookupAccount("")
235	if err != nil {
236		return user, errors.Wrap(err, "LookupAccountSid failed")
237	}
238
239	return user, nil
240}
241
242// GetDebugInfo returns general debug info about the current process.
243func GetDebugInfo() (*DebugInfo, error) {
244	h, err := windows.GetCurrentProcess()
245	if err != nil {
246		return nil, err
247	}
248
249	var token syscall.Token
250	err = syscall.OpenProcessToken(syscall.Handle(h), syscall.TOKEN_QUERY, &token)
251	if err != nil {
252		return nil, err
253	}
254
255	privs, err := GetTokenPrivileges(token)
256	if err != nil {
257		return nil, err
258	}
259
260	user, err := GetTokenUser(token)
261	if err != nil {
262		return nil, err
263	}
264
265	return &DebugInfo{
266		User:         user,
267		ProcessPrivs: privs,
268		OSVersion:    GetWindowsVersion(),
269		Arch:         runtime.GOARCH,
270		NumCPU:       runtime.NumCPU(),
271	}, nil
272}
273