CreateProcessAsUser возвращает C0000142 в одном сценарии, но работает в другом

У нас есть проблема, которая возникает только в новом сценарии, который мы должны поддерживать.

Вот сценарий, в котором он работает уже много лет:

  • Интерактивный пользователь без прав администратора запускает наш загрузчик wix, который запускает наш update-service, который затем запускает второй экземпляр загрузчика wix как local system. Этот второй экземпляр с разрешениями local system используется для установки нашего программного обеспечения. Поскольку нам также нужно выполнять некоторые действия с базой данных, мы запускаем инструмент под названием dbinit в пользовательском действии, и поскольку этот инструмент должен получить доступ к серверу sql, он должен работать с привилегиями исходного пользователя. Это прекрасно работает.

  • Теперь есть дополнительный сценарий, который мы должны поддерживать: это больше не интерактивный пользователь, который запускает наш загрузчик wix, теперь это служба Windows. Я думал, что это будет довольно просто и должно работать из коробки, но, боже мой, я ошибался. Вот диаграмма последовательности нового сценария:

Как видите, в новом сценарии CreateProcessAsUser также завершается успешно, но dbinit-процесс тут же завершается с кодом ошибки C0000142.

Мы попытались создать пользовательский WindowStation/Desktop. Разрешения должны быть правильными (проверено с помощью ProcessHacker), и мы также пометили ручки как inheritable. Но независимо от того, являются ли они inheritable или мы устанавливаем lpDesktop соответственно, это просто ничего не меняет.

Мы выяснили, что если пользователь, под которым запущена служба Windows, входит в группу локальных администраторов, она работает, но мы не можем этого сделать в продакшене.

Во многих примерах кода я обнаружил, что люди используют LogonUser для получения токена, но поскольку наш пользователь большую часть времени является MSA (управляемой учетной записью службы) и у нас нет паролей, я не думаю, что это возможно.

ОБНОВЛЕНИЕ 1:

Если я устанавливаю lpDesktop на пустую строку, она работает примерно в 50% случаев, поэтому кажется, что она должна что-то делать с WindowStation и Desktop. Чего я не понимаю, так это почему это не работает последовательно и почему это не помогает создавать собственные WindowStation и Desktop с соответствующими правами.

ОБНОВЛЕНИЕ 2:

Некоторый код:

internal static SafeTokenHandle GetProcessAccessToken(int processId)
{
    var process = Process.GetProcessById(processId);
    if (OpenProcessToken(process.Handle, TOKEN_DUPLICATE, out IntPtr tokenHandle))
        return new SafeTokenHandle(tokenHandle);
    else
        throw new Win32Exception();
}

internal static SafeTokenHandle DuplicateAccessToken(SafeTokenHandle token)
{
    var success = DuplicateTokenEx(token,
                                    TOKEN_ALL_ACCESS,
                                    null,
                                    IMPERSONATION_LEVEL_SecurityIdentification,
                                    TOKEN_TYPE_TokenPrimary,
                                    out IntPtr newToken);


    return success ? new SafeTokenHandle(newToken) : throw new Win32Exception();
}

private bool Start()
{
    using (var processToken = GetProcessAccessToken(StartInfo.ProcessIdToImpersonateUserContext))
    {
        using (var newToken = DuplicateAccessToken(processToken))
        {
            var si = new STARTUPINFO();
            var pi = new PROCESS_INFORMATION();

            var safeProcessHandle = new SafeProcessHandle();
            var safeThreadHandle = new SafeThreadHandle();

            SafeFileHandle redirectedStandardOutputParentHandle = null;

            try
            {
                var profileInfo = new PROFILEINFO();
                profileInfo.dwSize = Marshal.SizeOf(profileInfo);
                profileInfo.lpUserName = "LimitedUser";

                var succeeded = LoadUserProfile(newToken, ref profileInfo);
                if (!succeeded)
                    throw new Win32Exception();

                var cmdLine = $"\"{StartInfo.FileName}\" {StartInfo.Arguments}".Trim();

                if (StartInfo.RedirectStandardOutput)
                {
                    CreatePipe(out redirectedStandardOutputParentHandle, out si.hStdOutput);
                    si.dwFlags = STARTF_USESTDHANDLES;
                }

                int creationFlags = 0;
                if (StartInfo.CreateNoWindow)
                    creationFlags |= CREATE_NO_WINDOW;

                creationFlags |= CREATE_UNICODE_ENVIRONMENT;

                int logonFlags = 0;
                if (StartInfo.LoadUserProfile)
                    logonFlags |= (int)LogonFlags.LOGON_WITH_PROFILE;

                string workingDirectory = StartInfo.WorkingDirectory;
                if (string.IsNullOrEmpty(workingDirectory))
                    workingDirectory = Environment.CurrentDirectory;

                var envBlock = GetEnvironmentBlock(newToken);

                succeeded = CreateProcessAsUserW(newToken,
                                                    null,
                                                    cmdLine,
                                                    null,
                                                    null,
                                                    true,
                                                    creationFlags,
                                                    new HandleRef(null, envBlock.DangerousGetHandle()),
                                                    workingDirectory,
                                                    si,
                                                    pi);

                if (!succeeded)
                    throw new Win32Exception();
                if (pi.hProcess != (IntPtr)0 && pi.hProcess != INVALID_HANDLE_VALUE)
                    safeProcessHandle.InitialSetHandle(pi.hProcess);
                if (pi.hThread != (IntPtr)0 && pi.hThread != INVALID_HANDLE_VALUE)
                    safeThreadHandle.InitialSetHandle(pi.hThread);

                DestroyEnvironmentBlock(envBlock.DangerousGetHandle());
            }
            finally
            {
                si.Dispose();
            }

            if (StartInfo.RedirectStandardOutput)
            {
                var enc = StartInfo.StandardOutputEncoding ?? Console.OutputEncoding;
                StandardOutput = new StreamReader(new FileStream(redirectedStandardOutputParentHandle, FileAccess.Read, 4096, false), enc, true, 4096);
            }

            _processHandle = safeProcessHandle;

            safeThreadHandle.Dispose();

            return true;
        }
    }
}

Если вы не искали ошибку, ее символическая константа — STATUS_DLL_INIT_FAILED.

IInspectable 09.05.2023 17:08

C0000142 = STATUS_DLL_INIT_FAILED, что означает, что dbinit не может загрузить/инициализировать DLL. Попробуйте запустить свой сценарий либо с помощью Process Explorer, либо с помощью Windbg, чтобы выяснить, что не удается.

Luke 09.05.2023 17:09

запустите свой процесс (*dbinit *) в приостановленном состоянии. ищите его токен. проверьте, что отличается в обоих случаях. прикрепите отладчик. посмотрите, какой вызов не удался. часто это NtGdiInit

RbMm 09.05.2023 19:26

0xC0000142 STATUS_DLL_INIT_FAILED :{Ошибка инициализации DLL} Ошибка инициализации библиотеки динамической компоновки %hs. Процесс завершается аварийно. Я предлагаю вам попробовать добавить LoadUserProfile. Когда пользователь входит в систему в интерактивном режиме, система автоматически загружает профиль пользователя. Если служба или приложение выдает себя за пользователя, система не загружает профиль пользователя. Поэтому служба или приложение должны загрузить профиль пользователя с помощью LoadUserProfile.

Jeaninez - MSFT 10.05.2023 07:42

@Jeaninez-MSFT Спасибо за совет, но мы это уже пробовали. К сожалению, это ничего не меняет :(

roli09 10.05.2023 10:49

Я предлагаю вам обратиться к документу: Что случилось с ошибкой «Не удалось правильно инициализировать приложение (0xc0000142)»?

Jeaninez - MSFT 12.05.2023 07:44
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
104
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Мы до сих пор точно не знаем, почему Windows в нашем сценарии всегда использует winsta0\default, но мы думаем, что это как-то связано с участием rundll32.

Причина, по которой установка lpDesktop на пользовательские WindowStation и Desktop никогда не работала, заключается в том, что мы перепутали Ansi и Unicode. Как только мы явно установили CharSet на Unicode, все заработало. Вот код:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal class STARTUPINFO
{
    public int cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    ...
}

[DllImport(ADVAPI32, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)]
[SuppressUnmanagedCodeSecurity()]
internal extern static bool CreateProcessAsUser(SafeHandle hToken, string lpApplicationName, string lpCommandLine,
                                                SECURITY_ATTRIBUTES lpProcessAttributes, SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles,
                                                int dwCreationFlags, HandleRef lpEnvironment, string lpCurrentDirectory, STARTUPINFO lpStartupInfo,
                                                PROCESS_INFORMATION lpProcessInformation);

Теперь наше решение работает следующим образом:

  • Если процесс, из которого мы берем и дублируем токен, выполняется в сеансе 0, мы создаем собственные WindowStation и Desktop, чтобы не столкнуться с проблемами с разрешениями.
  • Если процесс не запускается в сеансе 0, мы оставляем все как было и работали годами, потому что тогда он использует winsta0\default, что совершенно нормально, потому что пользователь интерактивного процесса, похоже, имеет соответствующие разрешения.

Другие вопросы по теме