diff options
| author | Rider Linden <rider@lindenlab.com> | 2026-09-24 16:59:06 -0700 |
|---|---|---|
| committer | Rider Linden <rider@lindenlab.com> | 2026-09-24 16:59:06 -0700 |
| commit | 399859ac1689d1936dda2c6983785d49b6549055 (patch) | |
| tree | 2a5b5a35e1081aaa33599da522a853647481203c | |
| parent | da8aeb531171371109ff545544ea7b6ad4463bab (diff) | |
LLFile ignores the file bits on Windows. Explicitly create the file with an ACL restricting read to the current logged in user.
| -rw-r--r-- | indra/newview/llscripteditorws.cpp | 111 | ||||
| -rw-r--r-- | indra/newview/llscripteditorws.h | 6 |
2 files changed, 106 insertions, 11 deletions
diff --git a/indra/newview/llscripteditorws.cpp b/indra/newview/llscripteditorws.cpp index ddc3e25d7b..0a3e09fec0 100644 --- a/indra/newview/llscripteditorws.cpp +++ b/indra/newview/llscripteditorws.cpp @@ -75,6 +75,10 @@ #include <openssl/rand.h> +#if LL_WINDOWS +#include <aclapi.h> +#endif + namespace { // Per-operation timeouts (seconds) for coroutine-based async RPC handlers. @@ -2988,19 +2992,9 @@ LLScriptEditorWSConnection::Challenge LLScriptEditorWSConnection::writeChallenge // folder can be everyone's. const std::string file = LLFile::tmpdir() + "sl_script_challenge_" + LLUUID::generateNewID().asString() + ".tmp"; const std::string text = challenge.mSecret.asString(); - std::error_code ec; - LLFile out(file, LLFile::out | LLFile::noreplace, ec, 0600); - if (ec) - { - // Whatever is there by that name is somebody else's, and stays. - LL_WARNS("ScriptEditorWS") << "Unable to make challenge file " << file << ": " << ec.message() << LL_ENDL; - return {}; - } - const bool written = out.write(text.data(), static_cast<S64>(text.size()), ec) == static_cast<S64>(text.size()) && !ec; - if (out.close(ec) != 0 || !written) + if (!writeUserOnlyFile(file, text)) { LL_WARNS("ScriptEditorWS") << "Unable to write challenge file " << file << LL_ENDL; - LLFile::remove(file); return {}; } @@ -3008,3 +3002,98 @@ LLScriptEditorWSConnection::Challenge LLScriptEditorWSConnection::writeChallenge return challenge; } +// static +bool LLScriptEditorWSConnection::writeUserOnlyFile(const std::string& file, const std::string& text) +{ +#if LL_WINDOWS + // LLFile::open() ignores the POSIX permission bits on Windows -- its + // decode_attributes() only maps them to a normal/read-only file + // attribute, never to an ACL -- so a mode like 0600 there does not + // keep other local accounts from reading the file. Build a DACL + // explicitly, granting only the current user's SID access. + HANDLE token = nullptr; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) + { + LL_WARNS("ScriptEditorWS") << "Unable to open the process token for the challenge file's ACL" << LL_ENDL; + return false; + } + + DWORD needed = 0; + GetTokenInformation(token, TokenUser, nullptr, 0, &needed); + std::vector<BYTE> user_info(needed); + const bool got_user = needed != 0 && GetTokenInformation(token, TokenUser, user_info.data(), needed, &needed); + CloseHandle(token); + if (!got_user) + { + LL_WARNS("ScriptEditorWS") << "Unable to read the current user's SID for the challenge file's ACL" << LL_ENDL; + return false; + } + PSID user_sid = reinterpret_cast<PTOKEN_USER>(user_info.data())->User.Sid; + + EXPLICIT_ACCESSW ea = {}; + ea.grfAccessPermissions = GENERIC_READ | GENERIC_WRITE | DELETE; + ea.grfAccessMode = SET_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea.Trustee.TrusteeType = TRUSTEE_IS_USER; + ea.Trustee.ptstrName = reinterpret_cast<LPWSTR>(user_sid); + + PACL dacl = nullptr; + if (SetEntriesInAclW(1, &ea, nullptr, &dacl) != ERROR_SUCCESS) + { + LL_WARNS("ScriptEditorWS") << "Unable to build an ACL for the challenge file" << LL_ENDL; + return false; + } + + SECURITY_DESCRIPTOR sd; + const bool sd_ok = InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) && + SetSecurityDescriptorDacl(&sd, TRUE, dacl, FALSE); + if (!sd_ok) + { + LL_WARNS("ScriptEditorWS") << "Unable to build a security descriptor for the challenge file" << LL_ENDL; + LocalFree(dacl); + return false; + } + + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = &sd; + sa.bInheritHandle = FALSE; + + const std::wstring wfile = ll_convert<std::wstring>(file); + // CREATE_NEW: fails if the file already exists, matching LLFile::noreplace. + HANDLE handle = CreateFileW(wfile.c_str(), GENERIC_WRITE, 0, &sa, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + LocalFree(dacl); + if (handle == INVALID_HANDLE_VALUE) + { + return false; + } + + DWORD written = 0; + const bool ok = WriteFile(handle, text.data(), static_cast<DWORD>(text.size()), &written, nullptr) && + written == text.size(); + CloseHandle(handle); + if (!ok) + { + LLFile::remove(file); + } + return ok; +#else + // POSIX open() honors the mode bits directly. + std::error_code ec; + LLFile out(file, LLFile::out | LLFile::noreplace, ec, 0600); + if (ec) + { + return false; + } + const bool written = out.write(text.data(), static_cast<S64>(text.size()), ec) == static_cast<S64>(text.size()) && !ec; + const bool closed = out.close(ec) == 0; + if (!written || !closed) + { + LLFile::remove(file); + return false; + } + return true; +#endif +} + diff --git a/indra/newview/llscripteditorws.h b/indra/newview/llscripteditorws.h index 7a215f5715..9919800ebe 100644 --- a/indra/newview/llscripteditorws.h +++ b/indra/newview/llscripteditorws.h @@ -109,6 +109,12 @@ private: // Empty where no secret could be made or written. static Challenge writeChallenge(); + // Writes text to a new file at path, readable only by the current + // user. On Windows this means an explicit ACL, since LLFile::open() + // there ignores POSIX permission bits entirely. Fails if the file + // already exists. + static bool writeUserOnlyFile(const std::string& path, const std::string& text); + /** * @brief Handle the handshake response from the client * @param result The response data from the client containing client information |
