Files
orca/native/windows-security/src/addon.cc
T
Neil 3931660639 fix(windows): start the WSL holder outside Orca's jobs so a crash doesn't take it down (#36178)
* fix(windows): start the WSL holder outside Orca's jobs so a crash doesn't take it down

* test(windows): prove the job closed with a non-breakaway control before checking the holder
2026-10-29 18:57:42 -07:00

573 lines
23 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Native DACL IO avoids icacls.exe’s multi-second main-thread stalls (#26027) and EDR-scored
// /reset + /inheritance:r pattern (#25583). JS owns SID/flag policy; native reads/writes structure.
#include <napi.h>
#include <windows.h>
#include <aclapi.h>
#include <sddl.h>
#include <wincred.h>
#include "isolated-spawn.h"
#include <functional>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
namespace {
struct Win32Result {
DWORD code = ERROR_SUCCESS;
const char* syscall = "";
bool ok() const { return code == ERROR_SUCCESS; }
};
Win32Result Fail(const char* syscall, DWORD code) { return Win32Result{code, syscall}; }
struct LocalDeleter {
void operator()(void* pointer) const { LocalFree(pointer); }
};
using LocalPtr = std::unique_ptr<void, LocalDeleter>;
Napi::Error ToError(Napi::Env env, const Win32Result& result) {
return Napi::Error::New(
env, std::string(result.syscall) + " failed with Win32 error " + std::to_string(result.code));
}
Win32Result ParseSddl(const std::u16string& sddl, LocalPtr* descriptor) {
PSECURITY_DESCRIPTOR raw = nullptr;
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
reinterpret_cast<LPCWSTR>(sddl.c_str()), SDDL_REVISION_1, &raw, nullptr)) {
return Fail("ConvertStringSecurityDescriptorToSecurityDescriptorW", GetLastError());
}
descriptor->reset(raw);
return {};
}
// The descriptor's own protection bit decides: a replacement DACL carries the caller's choice, and
// a grant re-reads the object's, so adding one rule never flips inheritance on or off.
Win32Result DaclInformationFor(PSECURITY_DESCRIPTOR descriptor, SECURITY_INFORMATION* information) {
SECURITY_DESCRIPTOR_CONTROL control = 0;
DWORD revision = 0;
if (!GetSecurityDescriptorControl(descriptor, &control, &revision)) {
return Fail("GetSecurityDescriptorControl", GetLastError());
}
*information = DACL_SECURITY_INFORMATION | ((control & SE_DACL_PROTECTED)
? PROTECTED_DACL_SECURITY_INFORMATION
: UNPROTECTED_DACL_SECURITY_INFORMATION);
return {};
}
Win32Result ApplyDacl(std::u16string path, const std::u16string& sddl) {
LocalPtr descriptor;
auto parsed = ParseSddl(sddl, &descriptor);
if (!parsed.ok()) {
return parsed;
}
BOOL present = FALSE;
BOOL defaulted = FALSE;
PACL dacl = nullptr;
if (!GetSecurityDescriptorDacl(descriptor.get(), &present, &dacl, &defaulted)) {
return Fail("GetSecurityDescriptorDacl", GetLastError());
}
// A missing DACL means "everyone, full control"; never install that by accident.
if (!present || dacl == nullptr) {
return Fail("GetSecurityDescriptorDacl", ERROR_INVALID_SECURITY_DESCR);
}
SECURITY_INFORMATION information = 0;
auto control = DaclInformationFor(descriptor.get(), &information);
if (!control.ok()) {
return control;
}
DWORD status = SetNamedSecurityInfoW(reinterpret_cast<LPWSTR>(path.data()), SE_FILE_OBJECT,
information, nullptr, nullptr, dacl, nullptr);
return status == ERROR_SUCCESS ? Win32Result{} : Fail("SetNamedSecurityInfoW", status);
}
Win32Result GrantAccess(std::u16string path, const std::u16string& sid, DWORD mask,
bool inheritable, bool replace) {
PSECURITY_DESCRIPTOR rawDescriptor = nullptr;
PACL existing = nullptr;
DWORD status = GetNamedSecurityInfoW(reinterpret_cast<LPCWSTR>(path.c_str()), SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION, nullptr, nullptr, &existing,
nullptr, &rawDescriptor);
if (status != ERROR_SUCCESS) {
return Fail("GetNamedSecurityInfoW", status);
}
LocalPtr descriptor(rawDescriptor);
// A NULL DACL already grants everyone everything; merging into it would build a DACL holding
// only this rule and lock every other principal out.
if (existing == nullptr) {
return {};
}
PSID rawSid = nullptr;
if (!ConvertStringSidToSidW(reinterpret_cast<LPCWSTR>(sid.c_str()), &rawSid)) {
return Fail("ConvertStringSidToSidW", GetLastError());
}
LocalPtr trusteeSid(rawSid);
EXPLICIT_ACCESSW access{};
access.grfAccessPermissions = mask;
access.grfAccessMode = replace ? SET_ACCESS : GRANT_ACCESS;
access.grfInheritance = inheritable ? SUB_CONTAINERS_AND_OBJECTS_INHERIT : NO_INHERITANCE;
access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
access.Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
access.Trustee.ptstrName = reinterpret_cast<LPWSTR>(rawSid);
PACL merged = nullptr;
status = SetEntriesInAclW(1, &access, existing, &merged);
if (status != ERROR_SUCCESS) {
return Fail("SetEntriesInAclW", status);
}
LocalPtr mergedAcl(merged);
SECURITY_INFORMATION information = 0;
auto control = DaclInformationFor(descriptor.get(), &information);
if (!control.ok()) {
return control;
}
status = SetNamedSecurityInfoW(reinterpret_cast<LPWSTR>(path.data()), SE_FILE_OBJECT,
information, nullptr, nullptr, merged, nullptr);
return status == ERROR_SUCCESS ? Win32Result{} : Fail("SetNamedSecurityInfoW", status);
}
Win32Result VerifyFileDescriptor(HANDLE file, PSECURITY_DESCRIPTOR expected) {
PSECURITY_DESCRIPTOR raw = nullptr;
PACL actual = nullptr;
DWORD status = GetSecurityInfo(file, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
nullptr, nullptr, &actual, nullptr, &raw);
LocalPtr owned(raw);
if (status != ERROR_SUCCESS) return Fail("GetSecurityInfo", status);
BOOL present = FALSE, defaulted = FALSE;
PACL requested = nullptr;
SECURITY_DESCRIPTOR_CONTROL control = 0;
DWORD revision = 0;
if (!GetSecurityDescriptorDacl(expected, &present, &requested, &defaulted) ||
!GetSecurityDescriptorControl(raw, &control, &revision) || !present || !requested ||
!actual || !(control & SE_DACL_PROTECTED) || actual->AclSize != requested->AclSize ||
std::memcmp(actual, requested, requested->AclSize) != 0) {
return Fail("VerifyFileDescriptor", ERROR_INVALID_SECURITY_DESCR);
}
return {};
}
Win32Result CreateFileWithDacl(const std::u16string& path, const std::u16string& sddl,
const uint8_t* data, size_t length, bool strict) {
LocalPtr descriptor;
auto parsed = ParseSddl(sddl, &descriptor);
if (!parsed.ok()) {
return parsed;
}
if (length > MAXDWORD) {
return Fail("WriteFile", ERROR_INVALID_PARAMETER);
}
SECURITY_ATTRIBUTES attributes{sizeof(attributes), descriptor.get(), FALSE};
// CREATE_NEW ensures the descriptor applies (existing files retain their DACL); share mode 0 excludes readers until complete.
HANDLE file = CreateFileW(reinterpret_cast<LPCWSTR>(path.c_str()),
GENERIC_WRITE | (strict ? READ_CONTROL : 0), 0,
&attributes, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) {
return Fail("CreateFileW", GetLastError());
}
Win32Result result = strict ? VerifyFileDescriptor(file, descriptor.get()) : Win32Result{};
DWORD written = 0;
if (result.ok() && !WriteFile(file, data, static_cast<DWORD>(length), &written, nullptr)) {
result = Fail("WriteFile", GetLastError());
} else if (result.ok() && written != length) {
result = Fail("WriteFile", ERROR_WRITE_FAULT);
}
if (result.ok() && strict) {
if (!FlushFileBuffers(file)) result = Fail("FlushFileBuffers", GetLastError());
else result = VerifyFileDescriptor(file, descriptor.get());
}
// A failed close can mean lost data on a network volume, so it tears the file like a failed write.
if (!CloseHandle(file) && result.ok()) {
result = Fail("CloseHandle", GetLastError());
}
if (!result.ok()) {
// A torn temp file is useless to the caller and must not linger beside the real one.
DeleteFileW(reinterpret_cast<LPCWSTR>(path.c_str()));
}
return result;
}
bool ReadGrantOptions(const Napi::Value& value, bool* inheritable, bool* replace) {
if (!value.IsObject()) {
return false;
}
auto options = value.As<Napi::Object>();
auto inherit = options.Get("inheritable");
auto replaceValue = options.Get("replace");
if (!inherit.IsBoolean() || !replaceValue.IsBoolean()) {
return false;
}
*inheritable = inherit.As<Napi::Boolean>().Value();
*replace = replaceValue.As<Napi::Boolean>().Value();
return true;
}
Napi::Value ThrowTypeError(Napi::Env env, const char* signature) {
Napi::TypeError::New(env, signature).ThrowAsJavaScriptException();
return env.Undefined();
}
Napi::Value SidString(Napi::Env env, PSID sid) {
LPWSTR text = nullptr;
if (!ConvertSidToStringSidW(sid, &text)) {
ToError(env, Fail("ConvertSidToStringSidW", GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
LocalPtr owned(text);
return Napi::String::New(env, reinterpret_cast<char16_t*>(text));
}
Napi::Value CurrentUserSid(const Napi::CallbackInfo& info) {
auto env = info.Env();
HANDLE token = nullptr;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
ToError(env, Fail("OpenProcessToken", GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
DWORD size = 0;
GetTokenInformation(token, TokenUser, nullptr, 0, &size);
std::vector<BYTE> buffer(size);
BOOL queried = size > 0 && GetTokenInformation(token, TokenUser, buffer.data(), size, &size);
DWORD queryError = GetLastError();
CloseHandle(token);
if (!queried) {
ToError(env, Fail("GetTokenInformation", queryError)).ThrowAsJavaScriptException();
return env.Undefined();
}
return SidString(env, reinterpret_cast<TOKEN_USER*>(buffer.data())->User.Sid);
}
Napi::Value DaclValue(Napi::Env env, PSECURITY_DESCRIPTOR rawDescriptor, PACL dacl) {
if (dacl == nullptr) {
return env.Null();
}
SECURITY_DESCRIPTOR_CONTROL control = 0;
DWORD revision = 0;
if (!GetSecurityDescriptorControl(rawDescriptor, &control, &revision)) {
ToError(env, Fail("GetSecurityDescriptorControl", GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
auto aces = Napi::Array::New(env, dacl->AceCount);
for (DWORD index = 0; index < dacl->AceCount; index++) {
void* raw = nullptr;
if (!GetAce(dacl, index, &raw)) {
ToError(env, Fail("GetAce", GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
auto* header = static_cast<ACE_HEADER*>(raw);
auto ace = Napi::Object::New(env);
ace.Set("type", Napi::Number::New(env, header->AceType));
ace.Set("flags", Napi::Number::New(env, header->AceFlags));
// ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACE share this layout: Mask, then SidStart.
if (header->AceType == ACCESS_ALLOWED_ACE_TYPE || header->AceType == ACCESS_DENIED_ACE_TYPE) {
auto* plain = static_cast<ACCESS_ALLOWED_ACE*>(raw);
ace.Set("mask", Napi::Number::New(env, plain->Mask));
auto sid = SidString(env, &plain->SidStart);
if (env.IsExceptionPending()) {
return env.Undefined();
}
ace.Set("sid", sid);
} else {
ace.Set("mask", Napi::Number::New(env, 0));
ace.Set("sid", env.Null());
}
aces.Set(index, ace);
}
auto result = Napi::Object::New(env);
result.Set("protected", Napi::Boolean::New(env, (control & SE_DACL_PROTECTED) != 0));
result.Set("aces", aces);
return result;
}
// Return structure only; null means NULL DACL. Only plain allow/deny ACEs expose SIDs;
// unknown/object/callback/conditional types retain sid null so JS can refuse them.
Napi::Value GetDaclAces(const Napi::CallbackInfo& info) {
auto env = info.Env();
if (info.Length() < 1 || !info[0].IsString()) {
return ThrowTypeError(env, "getDaclAces(path: string)");
}
auto path = info[0].As<Napi::String>().Utf16Value();
PSECURITY_DESCRIPTOR rawDescriptor = nullptr;
PACL dacl = nullptr;
DWORD status = GetNamedSecurityInfoW(reinterpret_cast<LPCWSTR>(path.c_str()), SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION, nullptr, nullptr, &dacl,
nullptr, &rawDescriptor);
if (status != ERROR_SUCCESS) {
ToError(env, Fail("GetNamedSecurityInfoW", status)).ThrowAsJavaScriptException();
return env.Undefined();
}
LocalPtr descriptor(rawDescriptor);
return DaclValue(env, rawDescriptor, dacl);
}
Napi::Value SetDaclSddl(const Napi::CallbackInfo& info) {
auto env = info.Env();
if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
return ThrowTypeError(env, "setDaclSddl(path: string, sddl: string)");
}
auto result = ApplyDacl(info[0].As<Napi::String>().Utf16Value(),
info[1].As<Napi::String>().Utf16Value());
if (!result.ok()) {
ToError(env, result).ThrowAsJavaScriptException();
}
return env.Undefined();
}
Napi::Value ReadFileWithDacl(const Napi::CallbackInfo& info) {
auto env = info.Env();
if (info.Length() < 2 || !info[0].IsString() || !info[1].IsNumber())
return ThrowTypeError(env, "readFileWithDacl(path: string, maxBytes: number)");
auto path = info[0].As<Napi::String>().Utf16Value();
DWORD limit = info[1].As<Napi::Number>().Uint32Value();
HANDLE file = CreateFileW(reinterpret_cast<LPCWSTR>(path.c_str()), GENERIC_READ | READ_CONTROL,
FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
if (file == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) return env.Null();
ToError(env, Fail("CreateFileW", error)).ThrowAsJavaScriptException();
return env.Undefined();
}
BY_HANDLE_FILE_INFORMATION attributes{};
LARGE_INTEGER size{};
PSECURITY_DESCRIPTOR raw = nullptr;
PACL dacl = nullptr;
Win32Result result;
if (GetFileType(file) != FILE_TYPE_DISK || !GetFileInformationByHandle(file, &attributes) ||
(attributes.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) ||
!GetFileSizeEx(file, &size) || size.QuadPart < 0 || size.QuadPart > limit) {
result = Fail("ReadFileWithDacl", ERROR_INVALID_DATA);
}
if (result.ok()) {
DWORD status = GetSecurityInfo(file, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
nullptr, nullptr, &dacl, nullptr, &raw);
if (status != ERROR_SUCCESS) result = Fail("GetSecurityInfo", status);
}
LocalPtr descriptor(raw);
auto output = Napi::Object::New(env);
if (result.ok()) {
output.Set("dacl", DaclValue(env, raw, dacl));
auto bytes = Napi::Buffer<uint8_t>::New(env, static_cast<size_t>(size.QuadPart));
DWORD read = 0;
if (!env.IsExceptionPending() &&
(!ReadFile(file, bytes.Data(), static_cast<DWORD>(size.QuadPart), &read, nullptr) ||
read != size.QuadPart)) result = Fail("ReadFile", ERROR_READ_FAULT);
output.Set("data", bytes);
}
if (!CloseHandle(file) && result.ok()) result = Fail("CloseHandle", GetLastError());
if (!result.ok()) ToError(env, result).ThrowAsJavaScriptException();
return output;
}
struct CredentialDeleter {
void operator()(CREDENTIALW* value) const { CredFree(value); }
};
using CredentialPtr = std::unique_ptr<CREDENTIALW, CredentialDeleter>;
Napi::Value ReadGenericCredential(const Napi::CallbackInfo& info) {
auto env = info.Env();
if (info.Length() < 1 || !info[0].IsString())
return ThrowTypeError(env, "readGenericCredential(target: string)");
auto target = info[0].As<Napi::String>().Utf16Value();
PCREDENTIALW raw = nullptr;
if (!CredReadW(reinterpret_cast<LPCWSTR>(target.c_str()), CRED_TYPE_GENERIC, 0, &raw)) {
DWORD error = GetLastError();
if (error == ERROR_NOT_FOUND) return env.Null();
auto failure = ToError(env, Fail("CredReadW", error));
failure.Set("syscall", Napi::String::New(env, "CredReadW"));
failure.Set("win32Code", Napi::Number::New(env, error));
failure.ThrowAsJavaScriptException();
return env.Undefined();
}
CredentialPtr owned(raw);
return Napi::Buffer<BYTE>::Copy(env, raw->CredentialBlob, raw->CredentialBlobSize);
}
Napi::Value WriteGenericCredential(const Napi::CallbackInfo& info) {
auto env = info.Env();
if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsTypedArray())
return ThrowTypeError(env, "writeGenericCredential(target: string, user: string, data: Uint8Array)");
auto target = info[0].As<Napi::String>().Utf16Value();
auto user = info[1].As<Napi::String>().Utf16Value();
auto bytes = info[2].As<Napi::Uint8Array>();
if (bytes.ByteLength() > 2560) {
ToError(env, Fail("CredWriteW", ERROR_INVALID_PARAMETER)).ThrowAsJavaScriptException();
return env.Undefined();
}
PCREDENTIALW raw = nullptr;
if (!CredReadW(reinterpret_cast<LPCWSTR>(target.c_str()), CRED_TYPE_GENERIC, 0, &raw)) {
DWORD error = GetLastError();
if (error != ERROR_NOT_FOUND) {
ToError(env, Fail("CredReadW", error)).ThrowAsJavaScriptException();
return env.Undefined();
}
}
CredentialPtr owned(raw);
CREDENTIALW created{};
created.Type = CRED_TYPE_GENERIC;
created.TargetName = reinterpret_cast<LPWSTR>(target.data());
created.UserName = reinterpret_cast<LPWSTR>(user.data());
created.Persist = CRED_PERSIST_LOCAL_MACHINE;
// Replacing only the blob retains every writable field in an existing credential.
CREDENTIALW replacement = raw ? *raw : created;
PCREDENTIALW credential = &replacement;
credential->CredentialBlob = bytes.Data();
credential->CredentialBlobSize = static_cast<DWORD>(bytes.ByteLength());
if (!CredWriteW(credential, 0))
ToError(env, Fail("CredWriteW", GetLastError())).ThrowAsJavaScriptException();
return env.Undefined();
}
Napi::Value CreateFileWithDaclJs(const Napi::CallbackInfo& info) {
auto env = info.Env();
if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() ||
!info[2].IsTypedArray()) {
return ThrowTypeError(env, "createFileWithDacl(path: string, sddl: string, data: Uint8Array)");
}
auto data = info[2].As<Napi::Uint8Array>();
auto result = CreateFileWithDacl(info[0].As<Napi::String>().Utf16Value(),
info[1].As<Napi::String>().Utf16Value(), data.Data(),
data.ByteLength(), info.Length() > 3 && info[3].IsBoolean() &&
info[3].As<Napi::Boolean>().Value());
if (!result.ok()) {
ToError(env, result).ThrowAsJavaScriptException();
}
return env.Undefined();
}
bool ReadGrantArguments(const Napi::CallbackInfo& info, std::u16string* path, std::u16string* sid,
DWORD* mask, bool* inheritable, bool* replace) {
if (info.Length() < 4 || !info[0].IsString() || !info[1].IsString() || !info[2].IsNumber() ||
!ReadGrantOptions(info[3], inheritable, replace)) {
return false;
}
*path = info[0].As<Napi::String>().Utf16Value();
*sid = info[1].As<Napi::String>().Utf16Value();
*mask = info[2].As<Napi::Number>().Uint32Value();
return true;
}
constexpr const char* kGrantSignature =
"grantAccess(path: string, sid: string, accessMask: number, "
"options: { inheritable: boolean, replace: boolean })";
Napi::Value GrantAccessJs(const Napi::CallbackInfo& info) {
auto env = info.Env();
std::u16string path, sid;
DWORD mask = 0;
bool inheritable = false, replace = false;
if (!ReadGrantArguments(info, &path, &sid, &mask, &inheritable, &replace)) {
return ThrowTypeError(env, kGrantSignature);
}
auto result = GrantAccess(path, sid, mask, inheritable, replace);
if (!result.ok()) {
ToError(env, result).ThrowAsJavaScriptException();
}
return env.Undefined();
}
// Why a worker: a directory DACL change walks every descendant (seconds on a large userData).
class DaclWorker : public Napi::AsyncWorker {
public:
DaclWorker(Napi::Env env, std::function<Win32Result()> work)
: Napi::AsyncWorker(env), deferred_(Napi::Promise::Deferred::New(env)),
work_(std::move(work)) {}
Napi::Promise Promise() { return deferred_.Promise(); }
void Execute() override { result_ = work_(); }
void OnOK() override {
if (result_.ok()) {
deferred_.Resolve(Env().Undefined());
} else {
deferred_.Reject(ToError(Env(), result_).Value());
}
}
private:
Napi::Promise::Deferred deferred_;
std::function<Win32Result()> work_;
Win32Result result_;
};
Napi::Value SetDaclSddlAsync(const Napi::CallbackInfo& info) {
auto env = info.Env();
if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
return ThrowTypeError(env, "setDaclSddlAsync(path: string, sddl: string)");
}
auto path = info[0].As<Napi::String>().Utf16Value();
auto sddl = info[1].As<Napi::String>().Utf16Value();
auto* worker = new DaclWorker(env, [path, sddl]() { return ApplyDacl(path, sddl); });
auto promise = worker->Promise();
worker->Queue();
return promise;
}
Napi::Value GrantAccessAsync(const Napi::CallbackInfo& info) {
auto env = info.Env();
std::u16string path, sid;
DWORD mask = 0;
bool inheritable = false, replace = false;
if (!ReadGrantArguments(info, &path, &sid, &mask, &inheritable, &replace)) {
return ThrowTypeError(env, kGrantSignature);
}
auto* worker = new DaclWorker(env, [=]() {
return GrantAccess(path, sid, mask, inheritable, replace);
});
auto promise = worker->Promise();
worker->Queue();
return promise;
}
// Clear HANDLE_FLAG_INHERIT on the std handles so a spawn with bInheritHandles=TRUE can't leak them
// to a long-lived child; libuv duplicates an inheritable copy for an explicit stdio 'inherit'.
// Returns only failures, as [{ stream, code }]; absent handles are skipped.
Napi::Value ClearStdHandleInheritance(const Napi::CallbackInfo& info) {
auto env = info.Env();
struct Stream {
DWORD id;
const char* name;
};
const Stream streams[] = {{STD_INPUT_HANDLE, "stdin"},
{STD_OUTPUT_HANDLE, "stdout"},
{STD_ERROR_HANDLE, "stderr"}};
auto failures = Napi::Array::New(env);
for (const auto& stream : streams) {
HANDLE handle = GetStdHandle(stream.id);
if (handle == nullptr || handle == INVALID_HANDLE_VALUE) {
continue;
}
if (!SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0)) {
auto failure = Napi::Object::New(env);
failure.Set("stream", stream.name);
failure.Set("code", static_cast<double>(GetLastError()));
failures.Set(failures.Length(), failure);
}
}
return failures;
}
} // namespace
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("currentUserSid", Napi::Function::New(env, CurrentUserSid));
exports.Set("getDaclAces", Napi::Function::New(env, GetDaclAces));
exports.Set("readFileWithDacl", Napi::Function::New(env, ReadFileWithDacl));
exports.Set("readGenericCredential", Napi::Function::New(env, ReadGenericCredential));
exports.Set("writeGenericCredential", Napi::Function::New(env, WriteGenericCredential));
exports.Set("setDaclSddl", Napi::Function::New(env, SetDaclSddl));
exports.Set("setDaclSddlAsync", Napi::Function::New(env, SetDaclSddlAsync));
exports.Set("createFileWithDacl", Napi::Function::New(env, CreateFileWithDaclJs));
exports.Set("grantAccess", Napi::Function::New(env, GrantAccessJs));
exports.Set("grantAccessAsync", Napi::Function::New(env, GrantAccessAsync));
exports.Set("clearStdHandleInheritance", Napi::Function::New(env, ClearStdHandleInheritance));
exports.Set("spawnDetachedIsolated", Napi::Function::New(env, SpawnDetachedIsolated));
exports.Set("isolatedSpawnBreaksAwayFromJob",
Napi::Function::New(env, IsolatedSpawnBreaksAwayFromJob));
return exports;
}
NODE_API_MODULE(orca_windows_security, Init)