Add a GetNamedPipeClientProcessId security check on supported OSs (Vista+)
Also: * Removed some obsoleted comments. * Factor out the Handle class. Rename to OwnedHandle. Merge some improvements from libwinpty-rewrite, including fixing move-assignment so that it closes the existing handle, if one exists.
This commit is contained in:
parent
383138a0cc
commit
825483fa99
@ -27,6 +27,7 @@ LIBWINPTY_OBJECTS = \
|
|||||||
build/libwinpty/shared/Buffer.o \
|
build/libwinpty/shared/Buffer.o \
|
||||||
build/libwinpty/shared/DebugClient.o \
|
build/libwinpty/shared/DebugClient.o \
|
||||||
build/libwinpty/shared/GenRandom.o \
|
build/libwinpty/shared/GenRandom.o \
|
||||||
|
build/libwinpty/shared/OwnedHandle.o \
|
||||||
build/libwinpty/shared/StringUtil.o \
|
build/libwinpty/shared/StringUtil.o \
|
||||||
build/libwinpty/shared/WindowsSecurity.o \
|
build/libwinpty/shared/WindowsSecurity.o \
|
||||||
build/libwinpty/shared/WinptyAssert.o \
|
build/libwinpty/shared/WinptyAssert.o \
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "../shared/AgentMsg.h"
|
#include "../shared/AgentMsg.h"
|
||||||
#include "../shared/Buffer.h"
|
#include "../shared/Buffer.h"
|
||||||
#include "../shared/GenRandom.h"
|
#include "../shared/GenRandom.h"
|
||||||
|
#include "../shared/OwnedHandle.h"
|
||||||
#include "../shared/StringBuilder.h"
|
#include "../shared/StringBuilder.h"
|
||||||
#include "../shared/StringUtil.h"
|
#include "../shared/StringUtil.h"
|
||||||
#include "../shared/WindowsSecurity.h"
|
#include "../shared/WindowsSecurity.h"
|
||||||
@ -257,7 +258,9 @@ static std::wstring getDesktopFullName()
|
|||||||
static void startAgentProcess(const BackgroundDesktop &desktop,
|
static void startAgentProcess(const BackgroundDesktop &desktop,
|
||||||
const std::wstring &controlPipeName,
|
const std::wstring &controlPipeName,
|
||||||
const std::wstring &dataPipeName,
|
const std::wstring &dataPipeName,
|
||||||
int cols, int rows)
|
int cols, int rows,
|
||||||
|
HANDLE &agentProcess,
|
||||||
|
DWORD &agentPid)
|
||||||
{
|
{
|
||||||
const std::wstring exePath = findAgentProgram();
|
const std::wstring exePath = findAgentProgram();
|
||||||
const std::wstring cmdline =
|
const std::wstring cmdline =
|
||||||
@ -295,8 +298,36 @@ static void startAgentProcess(const BackgroundDesktop &desktop,
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(pi.hProcess);
|
|
||||||
CloseHandle(pi.hThread);
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
|
agentProcess = pi.hProcess;
|
||||||
|
agentPid = pi.dwProcessId;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool verifyPipeClientPid(HANDLE serverPipe, DWORD agentPid)
|
||||||
|
{
|
||||||
|
const auto client = getNamedPipeClientProcessId(serverPipe);
|
||||||
|
const auto err = GetLastError();
|
||||||
|
const auto success = std::get<0>(client);
|
||||||
|
if (success == GetNamedPipeClientProcessId_Result::Success) {
|
||||||
|
const auto clientPid = std::get<1>(client);
|
||||||
|
if (clientPid != agentPid) {
|
||||||
|
trace("Security check failed: pipe client pid (%u) does not "
|
||||||
|
"match agent pid (%u)",
|
||||||
|
static_cast<unsigned int>(clientPid),
|
||||||
|
static_cast<unsigned int>(agentPid));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (success == GetNamedPipeClientProcessId_Result::UnsupportedOs) {
|
||||||
|
trace("Pipe client PID security check skipped: "
|
||||||
|
"GetNamedPipeClientProcessId unsupported on this OS version");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
trace("GetNamedPipeClientProcessId failed: %u",
|
||||||
|
static_cast<unsigned int>(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WINPTY_API winpty_t *winpty_open(int cols, int rows)
|
WINPTY_API winpty_t *winpty_open(int cols, int rows)
|
||||||
@ -323,7 +354,11 @@ WINPTY_API winpty_t *winpty_open(int cols, int rows)
|
|||||||
BackgroundDesktop desktop = setupBackgroundDesktop();
|
BackgroundDesktop desktop = setupBackgroundDesktop();
|
||||||
|
|
||||||
// Start the agent.
|
// Start the agent.
|
||||||
startAgentProcess(desktop, controlPipeName, dataPipeName, cols, rows);
|
HANDLE agentProcess = NULL;
|
||||||
|
DWORD agentPid = INFINITE;
|
||||||
|
startAgentProcess(desktop, controlPipeName, dataPipeName, cols, rows,
|
||||||
|
agentProcess, agentPid);
|
||||||
|
OwnedHandle autoClose(agentProcess);
|
||||||
|
|
||||||
// TODO: Frequently, I see the CreateProcess call return successfully,
|
// TODO: Frequently, I see the CreateProcess call return successfully,
|
||||||
// but the agent immediately dies. The following pipe connect calls then
|
// but the agent immediately dies. The following pipe connect calls then
|
||||||
@ -349,6 +384,13 @@ WINPTY_API winpty_t *winpty_open(int cols, int rows)
|
|||||||
// destroyed before the agent can connect with them.
|
// destroyed before the agent can connect with them.
|
||||||
restoreOriginalDesktop(desktop);
|
restoreOriginalDesktop(desktop);
|
||||||
|
|
||||||
|
// Check that the pipe clients are correct.
|
||||||
|
if (!verifyPipeClientPid(pc->controlPipe, agentPid) ||
|
||||||
|
!verifyPipeClientPid(pc->dataPipe, agentPid)) {
|
||||||
|
delete pc;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This comment is now out-of-date. The named pipes now have a DACL
|
// TODO: This comment is now out-of-date. The named pipes now have a DACL
|
||||||
// that should prevent arbitrary users from connecting, even just to read.
|
// that should prevent arbitrary users from connecting, even just to read.
|
||||||
//
|
//
|
||||||
@ -366,38 +408,6 @@ WINPTY_API winpty_t *winpty_open(int cols, int rows)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: On Windows Vista and forward, we could call
|
|
||||||
// GetNamedPipeClientProcessId and verify that the PID is correct. We could
|
|
||||||
// also pass the PIPE_REJECT_REMOTE_CLIENTS flag on newer OS's.
|
|
||||||
// TODO: I suppose this code is still subject to a denial-of-service attack
|
|
||||||
// from untrusted accounts making read-only connections to the pipe. It
|
|
||||||
// should probably provide a SECURITY_DESCRIPTOR for the pipe, but the last
|
|
||||||
// time I tried that (using SDDL), I couldn't get it to work (access denied
|
|
||||||
// errors).
|
|
||||||
|
|
||||||
// Aside: An obvious way to setup these handles is to open both ends of the
|
|
||||||
// pipe in the parent process and let the child inherit its handles.
|
|
||||||
// Unfortunately, the Windows API makes inheriting handles problematic.
|
|
||||||
// MSDN says that handles have to be marked inheritable, and once they are,
|
|
||||||
// they are inherited by any call to CreateProcess with
|
|
||||||
// bInheritHandles==TRUE. To avoid accidental inheritance, the library's
|
|
||||||
// clients would be obligated not to create new processes while a thread
|
|
||||||
// was calling winpty_open. Moreover, to inherit handles, MSDN seems
|
|
||||||
// to say that bInheritHandles must be TRUE[*], but I don't want to use a
|
|
||||||
// TRUE bInheritHandles, because I want to avoid leaking handles into the
|
|
||||||
// agent process, especially if the library someday allows creating the
|
|
||||||
// agent process under a different user account.
|
|
||||||
//
|
|
||||||
// [*] The way that bInheritHandles and STARTF_USESTDHANDLES work together
|
|
||||||
// is unclear in the documentation. On one hand, for STARTF_USESTDHANDLES,
|
|
||||||
// it says that bInheritHandles must be TRUE. On Vista and up, isn't
|
|
||||||
// PROC_THREAD_ATTRIBUTE_HANDLE_LIST an acceptable alternative to
|
|
||||||
// bInheritHandles? On the other hand, KB315939 contradicts the
|
|
||||||
// STARTF_USESTDHANDLES documentation by saying, "Your pipe handles will
|
|
||||||
// still be duplicated because Windows will always duplicate the STD
|
|
||||||
// handles, even when bInheritHandles is set to FALSE." IIRC, my testing
|
|
||||||
// showed that the KB article was correct.
|
|
||||||
|
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
src/shared/OwnedHandle.cc
Executable file
36
src/shared/OwnedHandle.cc
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2016 Ryan Prichard
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to
|
||||||
|
// deal in the Software without restriction, including without limitation the
|
||||||
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
// sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
// IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#include "OwnedHandle.h"
|
||||||
|
|
||||||
|
#include "DebugClient.h"
|
||||||
|
#include "WinptyException.h"
|
||||||
|
|
||||||
|
void OwnedHandle::dispose(bool nothrow) {
|
||||||
|
if (m_h != nullptr && m_h != INVALID_HANDLE_VALUE) {
|
||||||
|
if (!CloseHandle(m_h)) {
|
||||||
|
trace("CloseHandle(%p) failed", m_h);
|
||||||
|
if (!nothrow) {
|
||||||
|
throwWindowsError(L"CloseHandle failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_h = nullptr;
|
||||||
|
}
|
45
src/shared/OwnedHandle.h
Executable file
45
src/shared/OwnedHandle.h
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) 2016 Ryan Prichard
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to
|
||||||
|
// deal in the Software without restriction, including without limitation the
|
||||||
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
// sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
// IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef WINPTY_SHARED_OWNED_HANDLE_H
|
||||||
|
#define WINPTY_SHARED_OWNED_HANDLE_H
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
class OwnedHandle {
|
||||||
|
HANDLE m_h;
|
||||||
|
public:
|
||||||
|
OwnedHandle() : m_h(nullptr) {}
|
||||||
|
explicit OwnedHandle(HANDLE h) : m_h(h) {}
|
||||||
|
~OwnedHandle() { dispose(true); }
|
||||||
|
void dispose(bool nothrow=false);
|
||||||
|
HANDLE get() const { return m_h; }
|
||||||
|
HANDLE release() { HANDLE ret = m_h; m_h = nullptr; return ret; }
|
||||||
|
OwnedHandle(const OwnedHandle &other) = delete;
|
||||||
|
OwnedHandle(OwnedHandle &&other) : m_h(other.release()) {}
|
||||||
|
OwnedHandle &operator=(const OwnedHandle &other) = delete;
|
||||||
|
OwnedHandle &operator=(OwnedHandle &&other) {
|
||||||
|
dispose();
|
||||||
|
m_h = other.release();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINPTY_SHARED_OWNED_HANDLE_H
|
@ -35,6 +35,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include "DebugClient.h"
|
#include "DebugClient.h"
|
||||||
#include "OsModule.h"
|
#include "OsModule.h"
|
||||||
|
#include "OwnedHandle.h"
|
||||||
#include "StringBuilder.h"
|
#include "StringBuilder.h"
|
||||||
#include "WinptyAssert.h"
|
#include "WinptyAssert.h"
|
||||||
#include "WinptyException.h"
|
#include "WinptyException.h"
|
||||||
@ -66,34 +67,12 @@ Sid allocatedSid(PSID v) {
|
|||||||
return Sid(v, std::unique_ptr<Impl>(new Impl { v }));
|
return Sid(v, std::unique_ptr<Impl>(new Impl { v }));
|
||||||
}
|
}
|
||||||
|
|
||||||
class Handle {
|
|
||||||
HANDLE m_h;
|
|
||||||
public:
|
|
||||||
explicit Handle(HANDLE h) : m_h(h) {}
|
|
||||||
~Handle() {
|
|
||||||
if (m_h != nullptr) {
|
|
||||||
CloseHandle(m_h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HANDLE get() const { return m_h; }
|
|
||||||
Handle(const Handle &other) = delete;
|
|
||||||
Handle &operator=(const Handle &other) = delete;
|
|
||||||
Handle(Handle &&other) : m_h(other.m_h) {
|
|
||||||
other.m_h = nullptr;
|
|
||||||
}
|
|
||||||
Handle &operator=(Handle &&other) {
|
|
||||||
m_h = other.m_h;
|
|
||||||
other.m_h = nullptr;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
// Returns a handle to the thread's effective security token. If the thread
|
// Returns a handle to the thread's effective security token. If the thread
|
||||||
// is impersonating another user, its token is returned, and otherwise, the
|
// is impersonating another user, its token is returned, and otherwise, the
|
||||||
// process' security token is opened. The handle is opened with TOKEN_QUERY.
|
// process' security token is opened. The handle is opened with TOKEN_QUERY.
|
||||||
static Handle openSecurityTokenForQuery() {
|
static OwnedHandle openSecurityTokenForQuery() {
|
||||||
HANDLE token = nullptr;
|
HANDLE token = nullptr;
|
||||||
// It is unclear to me whether OpenAsSelf matters for winpty, or what the
|
// It is unclear to me whether OpenAsSelf matters for winpty, or what the
|
||||||
// most appropriate value is.
|
// most appropriate value is.
|
||||||
@ -108,7 +87,7 @@ static Handle openSecurityTokenForQuery() {
|
|||||||
}
|
}
|
||||||
ASSERT(token != nullptr &&
|
ASSERT(token != nullptr &&
|
||||||
"OpenThreadToken/OpenProcessToken token is NULL");
|
"OpenThreadToken/OpenProcessToken token is NULL");
|
||||||
return Handle(token);
|
return OwnedHandle(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the TokenOwner of the thread's effective security token.
|
// Returns the TokenOwner of the thread's effective security token.
|
||||||
@ -117,7 +96,7 @@ Sid getOwnerSid() {
|
|||||||
std::unique_ptr<char[]> buffer;
|
std::unique_ptr<char[]> buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
Handle token = openSecurityTokenForQuery();
|
OwnedHandle token = openSecurityTokenForQuery();
|
||||||
DWORD actual = 0;
|
DWORD actual = 0;
|
||||||
BOOL success;
|
BOOL success;
|
||||||
success = GetTokenInformation(token.get(), TokenOwner,
|
success = GetTokenInformation(token.get(), TokenOwner,
|
||||||
@ -461,3 +440,27 @@ DWORD rejectRemoteClientsPipeFlag() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef BOOL WINAPI GetNamedPipeClientProcessId_t(
|
||||||
|
HANDLE Pipe,
|
||||||
|
PULONG ClientProcessId);
|
||||||
|
|
||||||
|
std::tuple<GetNamedPipeClientProcessId_Result, DWORD>
|
||||||
|
getNamedPipeClientProcessId(HANDLE serverPipe) {
|
||||||
|
OsModule kernel32(L"kernel32.dll");
|
||||||
|
const auto pGetNamedPipeClientProcessId =
|
||||||
|
reinterpret_cast<GetNamedPipeClientProcessId_t*>(
|
||||||
|
kernel32.proc("GetNamedPipeClientProcessId"));
|
||||||
|
if (pGetNamedPipeClientProcessId == nullptr) {
|
||||||
|
return std::make_tuple(
|
||||||
|
GetNamedPipeClientProcessId_Result::UnsupportedOs, 0);
|
||||||
|
}
|
||||||
|
ULONG pid = 0;
|
||||||
|
if (!pGetNamedPipeClientProcessId(serverPipe, &pid)) {
|
||||||
|
return std::make_tuple(
|
||||||
|
GetNamedPipeClientProcessId_Result::Failure, 0);
|
||||||
|
}
|
||||||
|
return std::make_tuple(
|
||||||
|
GetNamedPipeClientProcessId_Result::Success,
|
||||||
|
static_cast<DWORD>(pid));
|
||||||
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
// PSID and PSECURITY_DESCRIPTOR are both pointers to void, but we want
|
// PSID and PSECURITY_DESCRIPTOR are both pointers to void, but we want
|
||||||
@ -79,13 +80,25 @@ Sid wellKnownSid(
|
|||||||
Sid builtinAdminsSid();
|
Sid builtinAdminsSid();
|
||||||
Sid localSystemSid();
|
Sid localSystemSid();
|
||||||
Sid everyoneSid();
|
Sid everyoneSid();
|
||||||
|
|
||||||
SecurityDescriptor createPipeSecurityDescriptorOwnerFullControl();
|
SecurityDescriptor createPipeSecurityDescriptorOwnerFullControl();
|
||||||
SecurityDescriptor createPipeSecurityDescriptorOwnerFullControlEveryoneWrite();
|
SecurityDescriptor createPipeSecurityDescriptorOwnerFullControlEveryoneWrite();
|
||||||
SecurityDescriptor getObjectSecurityDescriptor(HANDLE handle);
|
SecurityDescriptor getObjectSecurityDescriptor(HANDLE handle);
|
||||||
|
|
||||||
std::wstring sidToString(PSID sid);
|
std::wstring sidToString(PSID sid);
|
||||||
Sid stringToSid(const std::wstring &str);
|
Sid stringToSid(const std::wstring &str);
|
||||||
SecurityDescriptor stringToSd(const std::wstring &str);
|
SecurityDescriptor stringToSd(const std::wstring &str);
|
||||||
std::wstring sdToString(PSECURITY_DESCRIPTOR sd);
|
std::wstring sdToString(PSECURITY_DESCRIPTOR sd);
|
||||||
|
|
||||||
DWORD rejectRemoteClientsPipeFlag();
|
DWORD rejectRemoteClientsPipeFlag();
|
||||||
|
|
||||||
|
enum class GetNamedPipeClientProcessId_Result {
|
||||||
|
Success,
|
||||||
|
Failure,
|
||||||
|
UnsupportedOs,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::tuple<GetNamedPipeClientProcessId_Result, DWORD>
|
||||||
|
getNamedPipeClientProcessId(HANDLE serverPipe);
|
||||||
|
|
||||||
#endif // WINPTY_WINDOWS_SECURITY_H
|
#endif // WINPTY_WINDOWS_SECURITY_H
|
||||||
|
@ -113,6 +113,8 @@
|
|||||||
'shared/DebugClient.cc',
|
'shared/DebugClient.cc',
|
||||||
'shared/GenRandom.h',
|
'shared/GenRandom.h',
|
||||||
'shared/GenRandom.cc',
|
'shared/GenRandom.cc',
|
||||||
|
'shared/OwnedHandle.h',
|
||||||
|
'shared/OwnedHandle.cc',
|
||||||
'shared/StringBuilder.h',
|
'shared/StringBuilder.h',
|
||||||
'shared/StringUtil.cc',
|
'shared/StringUtil.cc',
|
||||||
'shared/StringUtil.h',
|
'shared/StringUtil.h',
|
||||||
|
Loading…
Reference in New Issue
Block a user