You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows下Go程序以受限用户启动进程并关联Job Object问题

问题:Windows下以受限用户启动进程并分配到Job Object的Go实现问题

我在Windows Server 2022上运行一个以Administrator身份启动的Go应用,需求是以权限更受限的用户账户启动另一个应用,并将该进程分配到Job Object。

尝试了两种方案但都遇到问题:

  • 方案1:通过LogonUser获取令牌并传入SysProcAttr.Token
    调用LogonUser获取用户令牌后启动应用,报错:

    Error running app: fork/exec ./main8.exe: A required privilege is not held by the client.
    

    猜测缺少SE_INCREASE_QUOTA_NAME和SE_ASSIGNPRIMARYTOKEN_NAME权限,尝试用DuplicateTokenEx设置访问权限但无效。

  • 方案2:调用CreateProcessWithLogonW
    成功以受限用户启动进程,但无法将进程分配到Job Object,报错Access is denied,原因是该进程已被Secondary Logon Service分配到一个默认Job中。

请问我遗漏了什么?还有哪些解决方法可以尝试?


方案1的问题修复:正确处理令牌与权限

你猜测的权限方向是对的,但操作流程有误,需按以下步骤调整:

  1. 先启用当前Admin进程的目标权限
    SE_INCREASE_QUOTA_NAME和SE_ASSIGNPRIMARYTOKEN_NAME需要在当前进程中先启用,而非通过DuplicateTokenEx设置到用户令牌上。要调用AdjustTokenPrivileges将这两个权限从禁用状态改为启用。

  2. 将模拟令牌转换为主令牌
    LogonUser返回的是模拟令牌(Impersonation Token),而SysProcAttr.Token要求传入主令牌(Primary Token)。必须用DuplicateTokenEx指定TokenPrimary参数完成转换。

  3. 确保令牌拥有足够权限
    复制令牌时需指定TOKEN_ALL_ACCESS或至少包含PROCESS_CREATE_PROCESS的权限掩码。

关键Go代码片段:

import (
    "syscall"
    "os/exec"
    "golang.org/x/sys/windows"
)

// 启用指定进程权限
func enablePrivilege(privName string) error {
    hToken, err := windows.OpenCurrentProcessToken(windows.TOKEN_ADJUST_PRIVILEGES | windows.TOKEN_QUERY)
    if err != nil {
        return err
    }
    defer windows.CloseHandle(hToken)

    var luid windows.LUID
    if err := windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr(privName), &luid); err != nil {
        return err
    }

    tp := windows.TokenPrivileges{
        PrivilegeCount: 1,
        Privileges: [1]windows.LUIDAndAttributes{
            {Luid: luid, Attributes: windows.SE_PRIVILEGE_ENABLED},
        },
    }

    return windows.AdjustTokenPrivileges(hToken, false, &tp, 0, nil, nil)
}

func startProcessWithRestrictedUser() error {
    // 启用必要权限
    if err := enablePrivilege(windows.SE_INCREASE_QUOTA_NAME); err != nil {
        return err
    }
    if err := enablePrivilege(windows.SE_ASSIGNPRIMARYTOKEN_NAME); err != nil {
        return err
    }

    // 调用LogonUser获取模拟令牌(此处需替换为实际的用户名、密码、域)
    var logonToken windows.Token
    err := windows.LogonUser(
        windows.StringToUTF16Ptr("RestrictedUser"),
        windows.StringToUTF16Ptr("."),
        windows.StringToUTF16Ptr("UserPassword"),
        windows.LOGON32_LOGON_INTERACTIVE,
        windows.LOGON32_PROVIDER_DEFAULT,
        &logonToken,
    )
    if err != nil {
        return err
    }
    defer windows.CloseHandle(logonToken)

    // 转换为主令牌
    var primaryToken windows.Token
    err = windows.DuplicateTokenEx(
        logonToken,
        windows.TOKEN_ALL_ACCESS,
        nil,
        windows.SecurityIdentification,
        windows.TokenPrimary,
        &primaryToken,
    )
    if err != nil {
        return err
    }
    defer windows.CloseHandle(primaryToken)

    // 启动进程
    cmd := exec.Command("./main8.exe")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Token: syscall.Token(primaryToken),
    }
    return cmd.Start()
}

方案2的问题修复:避免系统Job冲突

CreateProcessWithLogonW会自动将进程放入Secondary Logon Service的系统Job,Windows不允许进程同时属于多个Job,因此会触发Access is denied。解决思路有两个:

  1. 改用CreateProcessAsUserW替代
    该API不会自动绑定系统Job,只要你能正确获取受限用户的主令牌(参考方案1的令牌处理流程),就可以直接启动进程并分配到自定义Job。

  2. 允许进程脱离系统Job
    若必须使用CreateProcessWithLogonW,可在创建自定义Job时设置JOB_OBJECT_LIMIT_BREAKAWAY_OK限制,允许进程从系统Job中脱离,再加入你的Job。注意需要确保系统Job的权限允许此操作。

关键代码片段(创建支持脱离的Job):

import "unsafe"
import "golang.org/x/sys/windows"

func createBreakawayJob() (windows.Handle, error) {
    hJob, err := windows.CreateJobObject(nil, nil)
    if err != nil {
        return 0, err
    }

    info := windows.JobObjectExtendedLimitInformation{
        BasicLimitInformation: windows.JobObjectBasicLimitInformation{
            LimitFlags: windows.JOB_OBJECT_LIMIT_BREAKAWAY_OK,
        },
    }

    err = windows.SetInformationJobObject(
        hJob,
        windows.JobObjectExtendedLimitInformation,
        &info,
        uint32(unsafe.Sizeof(info)),
    )
    if err != nil {
        windows.CloseHandle(hJob)
        return 0, err
    }

    return hJob, nil
}

// 在启动进程后调用AssignProcessToJobObject
// 需确保进程句柄拥有PROCESS_SET_QUOTA和PROCESS_TERMINATE权限

其他可选解决方法

  • 检查进程句柄权限:分配进程到Job时,必须持有进程句柄的PROCESS_SET_QUOTA和PROCESS_TERMINATE权限,否则会触发权限拒绝错误。启动进程时需指定对应的权限掩码。
  • 预缓存主令牌:如果需要多次启动受限进程,可提前获取并缓存主令牌,避免重复执行LogonUser和权限调整的开销。
  • 指定桌面会话:启动进程时在STARTUPINFO中设置lpDesktop为winsta0\default,避免因桌面会话隔离导致的权限问题。

内容的提问来源于stack exchange,提问作者Lucas Lopes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 20:23:23