github.com/iDigitalFlame/xmt@v0.5.4/cmd/exec_windows.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  // Copyright (C) 2020 - 2023 iDigitalFlame
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU General Public License as published by
     8  // the Free Software Foundation, either version 3 of the License, or
     9  // any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU General Public License
    17  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    18  //
    19  
    20  package cmd
    21  
    22  import (
    23  	"context"
    24  	"io"
    25  	"os"
    26  	"os/exec"
    27  	"runtime"
    28  	"strings"
    29  	"sync"
    30  	"sync/atomic"
    31  	"syscall"
    32  	"unsafe"
    33  
    34  	"github.com/iDigitalFlame/xmt/cmd/filter"
    35  	"github.com/iDigitalFlame/xmt/device/winapi"
    36  	"github.com/iDigitalFlame/xmt/util/bugtrack"
    37  	"github.com/iDigitalFlame/xmt/util/xerr"
    38  )
    39  
    40  // NOTE(dij): This needs to be a var as if it's a const 'UpdateProcThreadAttribute'
    41  // will throw an access violation.
    42  //
    43  // 0x100100000000 - PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON |
    44  // PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
    45  var secProtect uint64 = 0x100100000000
    46  var envOnce struct {
    47  	r string
    48  	e []string
    49  	sync.Once
    50  }
    51  var verOnce struct {
    52  	_ [0]func()
    53  	sync.Once
    54  	e, a bool
    55  }
    56  
    57  type closer uintptr
    58  type file interface {
    59  	File() (*os.File, error)
    60  }
    61  type fileFd interface {
    62  	Fd() uintptr
    63  }
    64  type executable struct {
    65  	r                  *os.File
    66  	filter             *filter.Filter
    67  	title              string
    68  	user, domain, pass string
    69  	closers            []io.Closer
    70  	i                  winapi.ProcessInformation
    71  	token, parent, m   uintptr
    72  	sf, x, y, w, h     uint32
    73  	mode               uint16
    74  }
    75  
    76  func envOnceFunc() {
    77  	envOnce.e = syscall.Environ()[4:] // Removes all '=' prefixed vars
    78  	if envOnce.r, _ = syscall.Getenv(sysRoot); len(envOnce.e) == 0 || len(envOnce.r) == 0 {
    79  		var (
    80  			v = winapi.SystemDirectory()
    81  			x = strings.LastIndexByte(v, '\\')
    82  		)
    83  		if x > 6 {
    84  			envOnce.r = v[:x]
    85  		} else {
    86  			envOnce.r = v
    87  		}
    88  	}
    89  }
    90  func verOnceFunc() {
    91  	if m, x, _ := winapi.GetVersionNumbers(); m > 6 {
    92  		verOnce.e, verOnce.a = true, true
    93  	} else {
    94  		verOnce.e, verOnce.a = m >= 6 && x >= 3, m >= 6
    95  	}
    96  }
    97  func checkVersion() bool {
    98  	verOnce.Do(verOnceFunc)
    99  	return verOnce.a
   100  }
   101  func checkVersionSec() bool {
   102  	verOnce.Do(verOnceFunc)
   103  	return verOnce.e
   104  }
   105  func (e *executable) close() {
   106  	if atomic.LoadUintptr(&e.i.Process) == 0 {
   107  		return
   108  	}
   109  	for i := range e.closers {
   110  		e.closers[i].Close()
   111  	}
   112  	e.parent, e.closers = 0, nil
   113  	if atomic.StoreUintptr(&e.i.Process, 0); e.m > 0 {
   114  		winapi.SetEvent(e.m)
   115  	}
   116  }
   117  func (c closer) Close() error {
   118  	return winapi.CloseHandle(uintptr(c))
   119  }
   120  func wait(h, m uintptr) error {
   121  	if m == 0 {
   122  		if _, err := winapi.WaitForSingleObject(h, -1); err != nil {
   123  			return err
   124  		}
   125  		return nil
   126  	}
   127  	if _, err := winapi.WaitForMultipleObjects([]uintptr{h, m}, false, -1); err != nil {
   128  		return err
   129  	}
   130  	return nil
   131  }
   132  func (e *executable) Pid() uint32 {
   133  	return e.i.ProcessID
   134  }
   135  
   136  // ResumeProcess will attempt to resume the process via its PID. This will
   137  // attempt to resume the process using an OS-dependent syscall.
   138  //
   139  // This will not affect already running processes.
   140  func ResumeProcess(p uint32) error {
   141  	// 0x800 - PROCESS_SUSPEND_RESUME
   142  	h, err := winapi.OpenProcess(0x800, false, p)
   143  	if err != nil {
   144  		return err
   145  	}
   146  	err = winapi.NtResumeProcess(h)
   147  	winapi.CloseHandle(h)
   148  	return err
   149  }
   150  
   151  // SuspendProcess will attempt to suspend the process via its PID. This will
   152  // attempt to suspend the process using an OS-dependent syscall.
   153  //
   154  // This will not affect already suspended processes.
   155  func SuspendProcess(p uint32) error {
   156  	// 0x800 - PROCESS_SUSPEND_RESUME
   157  	h, err := winapi.OpenProcess(0x800, false, p)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	err = winapi.NtSuspendProcess(h)
   162  	winapi.CloseHandle(h)
   163  	return err
   164  }
   165  func (e *executable) Resume() error {
   166  	return winapi.NtResumeProcess(e.i.Process)
   167  }
   168  func (e *executable) Suspend() error {
   169  	return winapi.NtSuspendProcess(e.i.Process)
   170  }
   171  func (e *executable) isStarted() bool {
   172  	return atomic.LoadUint32(&e.i.ProcessID) > 0 || e.i.Process > 0
   173  }
   174  func (e *executable) isRunning() bool {
   175  	return e.i.Process > 0
   176  }
   177  func (e *executable) Handle() uintptr {
   178  	return e.i.Process
   179  }
   180  func pipe() (*os.File, *os.File, error) {
   181  	var (
   182  		p   [2]syscall.Handle
   183  		err = syscall.Pipe(p[:])
   184  	)
   185  	if err != nil {
   186  		return nil, nil, err
   187  	}
   188  	return newFile(p[0], "|0", "file"), newFile(p[1], "|1", "file"), nil
   189  }
   190  func (e *executable) SetToken(t uintptr) {
   191  	e.token = t
   192  }
   193  func writerCopy(w io.Writer, x *os.File) {
   194  	if bugtrack.Enabled {
   195  		defer bugtrack.Recover("cmd.writerCopy()")
   196  	}
   197  	io.Copy(w, x)
   198  }
   199  func readerCopy(y *os.File, r io.Reader) {
   200  	if bugtrack.Enabled {
   201  		defer bugtrack.Recover("cmd.readerCopy()")
   202  	}
   203  	io.Copy(y, r)
   204  	y.Close()
   205  }
   206  func (e *executable) SetFullscreen(f bool) {
   207  	// 0x20 - STARTF_RUNFULLSCREEN
   208  	if f {
   209  		e.sf |= 0x20
   210  	} else {
   211  		e.sf = e.sf &^ 0x20
   212  	}
   213  }
   214  func waitInner(w chan<- error, h, m uintptr) {
   215  	if atomic.LoadUintptr(&h) > 0 {
   216  		if bugtrack.Enabled {
   217  			defer bugtrack.Recover("cmd.waitInner()")
   218  		}
   219  		w <- wait(h, m)
   220  	}
   221  	close(w)
   222  }
   223  func (e *executable) SetWindowDisplay(m int) {
   224  	// 0x1 - STARTF_USESHOWWINDOW
   225  	if m < 0 {
   226  		e.sf = e.sf &^ 0x1
   227  	} else {
   228  		e.sf |= 0x1
   229  		e.mode = uint16(m)
   230  	}
   231  }
   232  func (e *executable) SetWindowTitle(s string) {
   233  	// 0x1000 - STARTF_TITLEISAPPID
   234  	if len(s) > 0 {
   235  		e.sf |= 0x1000
   236  		e.title = s
   237  	} else {
   238  		e.sf, e.title = e.sf&^0x1000, ""
   239  	}
   240  }
   241  func (e *executable) SetLogin(u, d, p string) {
   242  	if e.user, e.domain, e.pass = u, d, p; len(d) == 0 {
   243  		d = "."
   244  	}
   245  }
   246  func (executable) SetUID(_ int32, _ *Process) {}
   247  func (executable) SetGID(_ int32, _ *Process) {}
   248  func (e *executable) SetWindowSize(w, h uint32) {
   249  	// 0x2 - STARTF_USESIZE
   250  	e.sf |= 0x2
   251  	e.w, e.h = w, h
   252  }
   253  func (executable) SetNoWindow(h bool, p *Process) {
   254  	// 0x8000000 - CREATE_NO_WINDOW
   255  	if h {
   256  		p.flags |= 0x8000000
   257  	} else {
   258  		p.flags = p.flags &^ 0x8000000
   259  	}
   260  }
   261  func (executable) SetDetached(d bool, p *Process) {
   262  	// 0x8  - DETACHED_PROCESS
   263  	// 0x10 - CREATE_NEW_CONSOLE
   264  	if d {
   265  		p.flags = (p.flags | 0x8) &^ 0x10
   266  	} else {
   267  		p.flags = p.flags &^ 0x8
   268  	}
   269  }
   270  func (executable) SetChroot(_ string, _ *Process) {}
   271  func (executable) SetSuspended(s bool, p *Process) {
   272  	// 0x4 - CREATE_SUSPENDED
   273  	if s {
   274  		p.flags |= 0x4
   275  	} else {
   276  		p.flags = p.flags &^ 0x4
   277  	}
   278  }
   279  
   280  //go:linkname newFile os.newFile
   281  func newFile(h syscall.Handle, n, k string) *os.File
   282  func (e *executable) SetWindowPosition(x, y uint32) {
   283  	// 0x4 - STARTF_USEPOSITION
   284  	e.sf |= 0x4
   285  	e.x, e.y = x, y
   286  }
   287  func (*executable) SetNewConsole(c bool, p *Process) {
   288  	// 0x10 - CREATE_NEW_CONSOLE
   289  	if c {
   290  		p.flags |= 0x10
   291  	} else {
   292  		p.flags = p.flags &^ 0x10
   293  	}
   294  }
   295  func (e *executable) kill(x uint32, p *Process) error {
   296  	if p.exit = x; e.i.Process == 0 {
   297  		return p.err
   298  	}
   299  	return winapi.TerminateProcess(e.i.Process, x)
   300  }
   301  func createEnvBlock(env []string, split bool) []string {
   302  	if envOnce.Do(envOnceFunc); len(env) == 0 && !split {
   303  		return envOnce.e
   304  	}
   305  	r := make([]string, len(env), len(env)+len(envOnce.e))
   306  	if copy(r, env); !split {
   307  		// NOTE(dij): If split == true, do NOT add any env vars, but DO
   308  		//            check and add %SYSTEMROOT% if it doesn't exist in the
   309  		//            supplied block.
   310  		return append(r, envOnce.e...)
   311  	}
   312  	for i := range r {
   313  		if len(r) > 11 {
   314  			if x := strings.IndexByte(r[i], '='); x > 9 {
   315  				if strings.EqualFold(r[i][:x], sysRoot) {
   316  					return r
   317  				}
   318  			}
   319  		}
   320  	}
   321  	return append(r, sysRoot+"="+envOnce.r)
   322  }
   323  func (e *executable) wait(x context.Context, p *Process) {
   324  	if bugtrack.Enabled {
   325  		defer bugtrack.Recover("cmd.(*executable).wait()")
   326  	}
   327  	var (
   328  		w   = make(chan error)
   329  		err error
   330  	)
   331  	if e.m, err = winapi.CreateEvent(nil, false, false, ""); err != nil {
   332  		if bugtrack.Enabled {
   333  			bugtrack.Track("cmd.(*executable).wait(): Creating Event failed, falling back to single wait: %s", err.Error())
   334  		}
   335  		err = nil
   336  	}
   337  	go waitInner(w, e.i.Process, e.m)
   338  	select {
   339  	case err = <-w:
   340  	case <-x.Done():
   341  	}
   342  	if e.m > 0 {
   343  		winapi.CloseHandle(e.m)
   344  		e.m = 0
   345  	}
   346  	if err != nil {
   347  		p.stopWith(exitStopped, err)
   348  		return
   349  	}
   350  	if err2 := x.Err(); err2 != nil {
   351  		p.stopWith(exitStopped, err2)
   352  		return
   353  	}
   354  	if atomic.SwapUint32(&p.cookie, atomic.LoadUint32(&p.cookie)|cookieStopped)&cookieStopped != 0 || atomic.LoadUintptr(&e.i.Process) == 0 {
   355  		p.stopWith(0, nil)
   356  		return
   357  	}
   358  	if err = winapi.GetExitCodeProcess(e.i.Process, &p.exit); err != nil {
   359  		p.stopWith(exitStopped, err)
   360  		return
   361  	}
   362  	if p.exit != 0 {
   363  		p.stopWith(p.exit, &ExitError{Exit: p.exit})
   364  		return
   365  	}
   366  	p.stopWith(p.exit, nil)
   367  }
   368  func (e *executable) writer(w io.Writer) (uintptr, error) {
   369  	h, c, err := e.writerToHandle(w)
   370  	if err != nil {
   371  		return 0, err
   372  	}
   373  	return e.addRetHandle(c, h)
   374  }
   375  func (e *executable) reader(r io.Reader) (uintptr, error) {
   376  	h, c, err := e.readerToHandle(r)
   377  	if err != nil {
   378  		return 0, err
   379  	}
   380  	return e.addRetHandle(c, h)
   381  }
   382  func (e *executable) SetParent(f *filter.Filter, p *Process) {
   383  	if e.filter = f; f != nil {
   384  		e.SetNewConsole(true, p)
   385  	}
   386  }
   387  func (e *executable) StdinPipe(p *Process) (io.WriteCloser, error) {
   388  	var err error
   389  	if p.Stdin, e.r, err = pipe(); err != nil {
   390  		return nil, xerr.Wrap("unable to create Pipe", err)
   391  	}
   392  	e.closers = append(e.closers, p.Stdin.(io.Closer))
   393  	return e.r, nil
   394  }
   395  func (e *executable) StdoutPipe(p *Process) (io.ReadCloser, error) {
   396  	r, w, err := pipe()
   397  	if err != nil {
   398  		return nil, xerr.Wrap("unable to create Pipe", err)
   399  	}
   400  	p.Stdout = w
   401  	e.closers = append(e.closers, w)
   402  	return r, nil
   403  }
   404  func (e *executable) StderrPipe(p *Process) (io.ReadCloser, error) {
   405  	r, w, err := pipe()
   406  	if err != nil {
   407  		return nil, xerr.Wrap("unable to create Pipe", err)
   408  	}
   409  	p.Stderr = w
   410  	e.closers = append(e.closers, w)
   411  	return r, nil
   412  }
   413  func (e *executable) addRetHandle(c bool, h uintptr) (uintptr, error) {
   414  	if e.parent == 0 {
   415  		if c {
   416  			e.closers = append(e.closers, closer(h))
   417  		}
   418  		return h, nil
   419  	}
   420  	var (
   421  		n   uintptr
   422  		err = winapi.DuplicateHandle(winapi.CurrentProcess, h, e.parent, &n, 0, true, 0x2)
   423  		// 0x2 - DUPLICATE_SAME_ACCESS
   424  	)
   425  	if c {
   426  		winapi.CloseHandle(h)
   427  	}
   428  	if err != nil {
   429  		return 0, xerr.Wrap("DuplicateHandle", err)
   430  	}
   431  	return n, nil
   432  }
   433  func (e *executable) readerToHandle(r io.Reader) (uintptr, bool, error) {
   434  	if r == nil {
   435  		// 0 - READONLY
   436  		f, err := os.OpenFile(os.DevNull, 0, 0)
   437  		if err != nil {
   438  			return 0, false, xerr.Wrap("cannot open NULL device", err)
   439  		}
   440  		e.closers = append(e.closers, f)
   441  		return f.Fd(), false, nil
   442  	}
   443  	switch i := r.(type) {
   444  	case file:
   445  		f, err := i.File()
   446  		if err != nil {
   447  			return 0, false, xerr.Wrap("cannot obtain file handle", err)
   448  		}
   449  		// Closeable "c" is true, since this /should/ be a separate
   450  		// handle from the initial "File" type.
   451  		//
   452  		// NOTE(dij): Technically on Windows this will always fail
   453  		// See: https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/net/fd_windows.go;l=175
   454  		//
   455  		// BUT if we're going off the *nix implementation, it would be a
   456  		// duplicate handle, and safe to close.
   457  		return f.Fd(), true, nil
   458  	case fileFd:
   459  		return i.Fd(), false, nil
   460  	}
   461  	x, y, err := pipe()
   462  	if err != nil {
   463  		return 0, false, xerr.Wrap("cannot create Pipe", err)
   464  	}
   465  	e.closers = append(e.closers, x)
   466  	go readerCopy(y, r)
   467  	return x.Fd(), false, nil
   468  }
   469  func (e *executable) writerToHandle(w io.Writer) (uintptr, bool, error) {
   470  	if w == nil {
   471  		// 1 - WRITEONLY
   472  		f, err := os.OpenFile(os.DevNull, 1, 0)
   473  		if err != nil {
   474  			return 0, false, xerr.Wrap("cannot open NULL device", err)
   475  		}
   476  		e.closers = append(e.closers, f)
   477  		return f.Fd(), false, nil
   478  	}
   479  	switch i := w.(type) {
   480  	case file:
   481  		f, err := i.File()
   482  		if err != nil {
   483  			return 0, false, xerr.Wrap("cannot obtain file handle", err)
   484  		}
   485  		// Closeable "c" is true, since this /should/ be a separate handle from
   486  		// the initial "File" type.
   487  		//
   488  		// NOTE(dij): Technically on Windows this will always fail
   489  		// See: https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/net/fd_windows.go;l=175
   490  		//
   491  		// BUT if we're going off the *nix implementation, it would be a duplicate
   492  		// handle, and safe to close.
   493  		return f.Fd(), true, nil
   494  	case fileFd:
   495  		return i.Fd(), false, nil
   496  	}
   497  	x, y, err := pipe()
   498  	if err != nil {
   499  		return 0, false, xerr.Wrap("cannot create Pipe", err)
   500  	}
   501  	e.closers = append(e.closers, y)
   502  	e.closers = append(e.closers, x)
   503  	go writerCopy(w, x)
   504  	return y.Fd(), false, nil
   505  }
   506  func (e *executable) start(x context.Context, p *Process, sus bool) error {
   507  	r, err := exec.LookPath(p.Args[0])
   508  	if err != nil {
   509  		return err
   510  	}
   511  	v, y, err := e.startInfo()
   512  	if err != nil {
   513  		return err
   514  	}
   515  	if p.Stderr != nil || p.Stdout != nil || p.Stdin != nil {
   516  		var si, so, se uintptr
   517  		if si, err = e.reader(p.Stdin); err != nil {
   518  			if e.parent > 0 {
   519  				winapi.CloseHandle(e.parent)
   520  				e.parent = 0
   521  			}
   522  			return err
   523  		}
   524  		if so, err = e.writer(p.Stdout); err != nil {
   525  			if e.parent > 0 {
   526  				winapi.CloseHandle(e.parent)
   527  				e.parent = 0
   528  			}
   529  			return err
   530  		}
   531  		if p.Stderr == p.Stdout {
   532  			se = so
   533  		} else if se, err = e.writer(p.Stderr); err != nil {
   534  			if e.parent > 0 {
   535  				winapi.CloseHandle(e.parent)
   536  				e.parent = 0
   537  			}
   538  			return err
   539  		}
   540  		if v != nil {
   541  			v.StartupInfo.StdErr = se
   542  			v.StartupInfo.StdInput = si
   543  			v.StartupInfo.StdOutput = so
   544  			v.StartupInfo.Flags |= 0x100
   545  			// 0x100 - STARTF_USESTDHANDLES
   546  		} else if y != nil {
   547  			y.StdErr, y.StdInput, y.StdOutput = se, si, so
   548  			y.Flags |= 0x100
   549  			// 0x100 - STARTF_USESTDHANDLES
   550  		}
   551  	}
   552  	u := e.token
   553  	if runtime.LockOSThread(); u == 0 && e.parent == 0 && !winapi.IsWindowsXp() {
   554  		// NOTE(dij): Handle threads that currently have an impersonated Token
   555  		//            set. This will trigger this function call to use
   556  		//            'CreateProcessWithToken' instead of 'CreateProcess'.
   557  		//            This is only called IF there is no parent Process set, as
   558  		//            Windows permissions cause some fucky stuff to happen.
   559  		//            Failing silently is fine.
   560  		//
   561  		// NOTE(dij): Added a 'IsUserNetworkToken' token to check the Token origin
   562  		//            to see if it's an impersonated user token or a stolen elevated
   563  		//            process token, as impersonated user tokens do NOT like being
   564  		//            ran with 'CreateProcessWithToken'.
   565  		// 0xF01FF - TOKEN_ALL_ACCESS
   566  		if winapi.OpenThreadToken(winapi.CurrentThread, 0xF01FF, true, &u); u > 0 && winapi.IsUserNetworkToken(u) {
   567  			if winapi.CloseHandle(u); bugtrack.Enabled {
   568  				bugtrack.Track("cmd.(*executable).start(): Removing user login token.")
   569  			}
   570  			u = 0
   571  		}
   572  	}
   573  	if sus {
   574  		// 0x4 - CREATE_SUSPENDED
   575  		p.flags |= 0x4
   576  	}
   577  	if e.r != nil {
   578  		e.r.Close()
   579  		e.r = nil
   580  	}
   581  	var t uintptr
   582  	if winapi.OpenThreadToken(winapi.CurrentThread, 0xF01FF, true, &t) == nil && (len(e.user) > 0 || u > 0) {
   583  		if winapi.SetThreadToken(winapi.CurrentThread, 0); bugtrack.Enabled {
   584  			bugtrack.Track("cmd.(*executable).start(): Clearing thread impersonation token.")
   585  		}
   586  	}
   587  	// NOTE(dij): Should we use CreateEnvironmentBlock? We'd have to keep track
   588  	//            of the handle though.
   589  	switch z := createEnvBlock(p.Env, p.split); {
   590  	case len(e.user) > 0:
   591  		if bugtrack.Enabled {
   592  			bugtrack.Track("cmd.(*executable).start(): Using API call CreateProcessWithLogin for execution.")
   593  		}
   594  		// 0x0 - *shrug*
   595  		err = winapi.CreateProcessWithLogin(e.user, e.domain, e.pass, 0x0, r, strings.Join(p.Args, " "), p.flags, z, p.Dir, y, v, &e.i)
   596  	case u > 0:
   597  		if bugtrack.Enabled {
   598  			bugtrack.Track("cmd.(*executable).start(): Using API call CreateProcessWithToken for execution.")
   599  		}
   600  		// 0x2 - LOGON_NETCREDENTIALS_ONLY
   601  		err = winapi.CreateProcessWithToken(u, 0x2, r, strings.Join(p.Args, " "), p.flags, z, p.Dir, y, v, &e.i)
   602  	default:
   603  		if bugtrack.Enabled {
   604  			bugtrack.Track("cmd.(*executable).start(): Using API call CreateProcess for execution.")
   605  		}
   606  		err = winapi.CreateProcess(r, strings.Join(p.Args, " "), nil, nil, true, p.flags, z, p.Dir, y, v, &e.i)
   607  	}
   608  	if t > 0 {
   609  		winapi.SetThreadToken(winapi.CurrentThread, t)
   610  		winapi.CloseHandle(t)
   611  	}
   612  	if u > 0 && e.token == 0 {
   613  		winapi.CloseHandle(u)
   614  	}
   615  	if runtime.UnlockOSThread(); e.parent > 0 {
   616  		winapi.CloseHandle(e.parent)
   617  		e.parent = 0
   618  	}
   619  	if err != nil {
   620  		for i := range e.closers {
   621  			e.closers[i].Close()
   622  		}
   623  		return err
   624  	}
   625  	winapi.CloseHandle(e.i.Thread)
   626  	if e.closers = append(e.closers, closer(e.i.Process)); sus {
   627  		return nil
   628  	}
   629  	go e.wait(x, p)
   630  	return nil
   631  }
   632  func (e *executable) startInfo() (*winapi.StartupInfoEx, *winapi.StartupInfo, error) {
   633  	var (
   634  		x   winapi.StartupInfoEx
   635  		err error
   636  	)
   637  	e.close()
   638  	x.StartupInfo.XSize, x.StartupInfo.YSize = e.w, e.h
   639  	x.StartupInfo.Flags, x.StartupInfo.ShowWindow = e.sf, e.mode
   640  	if x.StartupInfo.X, x.StartupInfo.Y = e.x, e.y; len(e.title) > 0 {
   641  		if x.StartupInfo.Title, err = winapi.UTF16PtrFromString(e.title); err != nil {
   642  			return nil, nil, xerr.Wrap("cannot convert title", err)
   643  		}
   644  	}
   645  	// NOTE(dij): checkVersion(): Retruns false if the system is < Windows Vista
   646  	if x.StartupInfo.Cb = uint32(unsafe.Sizeof(x)); !checkVersion() {
   647  		return nil, &x.StartupInfo, nil
   648  	}
   649  	if e.filter != nil && !e.filter.Empty() {
   650  		// (old 0x100CC1 - SYNCHRONIZE | PROCESS_DUP_HANDLE | PROCESS_CREATE_PROCESS |
   651  		//                  PROCESS_QUERY_INFORMATION | PROCESS_SUSPEND_RESUME | PROCESS_TERMINATE)
   652  		// (old 0x4C0 - PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE | PROCESS_CREATE_PROCESS)
   653  		//
   654  		// 0x10C0 - PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE | PROCESS_QUERY_LIMITED_INFORMATION
   655  		if e.parent, err = e.filter.HandleFunc(0x10C0, nil); err != nil {
   656  			return nil, nil, err
   657  		}
   658  		// FIXED(dij): Apparently sometimes this isn't closed? It seems to /only/
   659  		//             happen during spawn? Look into this later.
   660  		//
   661  		//             FIX: Close handle immediately after spawning process!
   662  		// e.closers = append(e.closers, closer(e.parent))
   663  	}
   664  	var c uint32
   665  	// NOTE(dij): SecProtect isn't allowed until Windows 8.1 and Windows Server 2012R2
   666  	//            Thanks for the super small blurb of text on it Microsoft >:[
   667  	switch v := checkVersionSec(); {
   668  	case !v && e.parent == 0: // No sec and no parent
   669  		return nil, &x.StartupInfo, nil
   670  	case !v && e.parent > 0: // No sec, has parent (1 slot)
   671  		fallthrough
   672  	case v && e.parent == 0: // Has sec, no parent (1 slot)
   673  		c = 1
   674  	case v && e.parent > 0: // Has sec, has parent (2 slots)
   675  		c = 2
   676  	}
   677  	x.AttributeList = &winapi.StartupAttributes{Count: c}
   678  	if x.StartupInfo.Cb = uint32(unsafe.Sizeof(x)); e.parent > 0 {
   679  		// 0x20000 - PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
   680  		if err = winapi.UpdateProcThreadAttribute(x.AttributeList, 0x20000, unsafe.Pointer(&e.parent), uint64(unsafe.Sizeof(e.parent)), nil, nil); err != nil {
   681  			winapi.CloseHandle(e.parent)
   682  			e.parent, x.AttributeList = 0, nil
   683  			return nil, nil, xerr.Wrap("UpdateProcThreadAttribute", err)
   684  		}
   685  		c--
   686  	}
   687  	if c == 1 {
   688  		// 0x20007 - PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY
   689  		if err = winapi.UpdateProcThreadAttribute(x.AttributeList, 0x20007, unsafe.Pointer(&secProtect), uint64(unsafe.Sizeof(secProtect)), nil, nil); err != nil {
   690  			if x.AttributeList = nil; e.parent > 0 {
   691  				winapi.CloseHandle(e.parent)
   692  				e.parent = 0
   693  			}
   694  			return nil, nil, xerr.Wrap("UpdateProcThreadAttribute", err)
   695  		}
   696  	}
   697  	return &x, nil, nil
   698  }