external/source/exploits/CVE-2020-0787/CommonUtils/ReparsePoint.cpp
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http ://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "stdafx.h"
#include "ReparsePoint.h"
#include "ScopedHandle.h"
#include "typed_buffer.h"
#include <string>
#include <vector>
// Taken from ntifs.h
#define SYMLINK_FLAG_RELATIVE 1
typedef struct _REPARSE_DATA_BUFFER {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
UCHAR DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
#define REPARSE_DATA_BUFFER_HEADER_LENGTH FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)
#define IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) // winnt
#define IO_REPARSE_TAG_HSM (0xC0000004L) // winnt
#define IO_REPARSE_TAG_DRIVE_EXTENDER (0x80000005L)
#define IO_REPARSE_TAG_HSM2 (0x80000006L) // winnt
#define IO_REPARSE_TAG_SIS (0x80000007L) // winnt
#define IO_REPARSE_TAG_WIM (0x80000008L) // winnt
#define IO_REPARSE_TAG_CSV (0x80000009L) // winnt
#define IO_REPARSE_TAG_DFS (0x8000000AL) // winnt
#define IO_REPARSE_TAG_FILTER_MANAGER (0x8000000BL)
#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) // winnt
#define IO_REPARSE_TAG_IIS_CACHE (0xA0000010L)
#define IO_REPARSE_TAG_DFSR (0x80000012L) // winnt
#define IO_REPARSE_TAG_DEDUP (0x80000013L) // winnt
#define IO_REPARSE_TAG_APPXSTRM (0xC0000014L)
#define IO_REPARSE_TAG_NFS (0x80000014L) // winnt
#define IO_REPARSE_TAG_FILE_PLACEHOLDER (0x80000015L) // winnt
#define IO_REPARSE_TAG_DFM (0x80000016L)
#define IO_REPARSE_TAG_WOF (0x80000017L) // winnt
static int g_last_error = 0;
int ReparsePoint::GetLastError()
{
return g_last_error;
}
ScopedHandle OpenReparsePoint(const std::wstring& path, bool writable)
{
HANDLE h = CreateFile(path.c_str(),
GENERIC_READ | (writable ? GENERIC_WRITE : 0),
0,
0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
0);
if (h == INVALID_HANDLE_VALUE)
{
g_last_error = GetLastError();
char buffer[50];
sprintf_s(buffer, "%d", g_last_error);
MessageBoxA(NULL, buffer, "OpenReparsePoint() Failure", MB_OK);
}
return ScopedHandle(h, false);
}
static bool SetReparsePoint(const ScopedHandle& handle, typed_buffer_ptr<REPARSE_DATA_BUFFER>& reparse_buffer)
{
DWORD cb;
if (!handle.IsValid()) {
return false;
}
bool ret = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
reparse_buffer, reparse_buffer.size(), nullptr, 0, &cb, nullptr) == TRUE;
if (!ret)
{
g_last_error = GetLastError();
}
return ret;
}
static bool DeleteReparsePoint(const ScopedHandle& handle, PREPARSE_GUID_DATA_BUFFER reparse_buffer)
{
DWORD cb;
if (!handle.IsValid()) {
return false;
}
bool ret = DeviceIoControl(handle,
FSCTL_DELETE_REPARSE_POINT,
reparse_buffer,
REPARSE_GUID_DATA_BUFFER_HEADER_SIZE,
nullptr,
0,
&cb,
0) == TRUE;
if (!ret)
{
g_last_error = GetLastError();
char buffer[50];
sprintf_s(buffer, "%d", g_last_error);
MessageBoxA(NULL, buffer, "DeleteReparsePoint() Failure", MB_OK);
}
return ret;
}
typed_buffer_ptr<REPARSE_DATA_BUFFER> BuildMountPoint(const std::wstring& target, const std::wstring& printname)
{
const size_t target_byte_size = target.size() * 2;
const size_t printname_byte_size = printname.size() * 2;
const size_t path_buffer_size = target_byte_size + printname_byte_size + 8 + 4;
const size_t total_size = path_buffer_size + REPARSE_DATA_BUFFER_HEADER_LENGTH;
typed_buffer_ptr<REPARSE_DATA_BUFFER> buffer(total_size);
buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
buffer->ReparseDataLength = static_cast<USHORT>(path_buffer_size);
buffer->Reserved = 0;
buffer->MountPointReparseBuffer.SubstituteNameOffset = 0;
buffer->MountPointReparseBuffer.SubstituteNameLength = static_cast<USHORT>(target_byte_size);
memcpy(buffer->MountPointReparseBuffer.PathBuffer, target.c_str(), target_byte_size + 2);
buffer->MountPointReparseBuffer.PrintNameOffset = static_cast<USHORT>(target_byte_size + 2);
buffer->MountPointReparseBuffer.PrintNameLength = static_cast<USHORT>(printname_byte_size);
memcpy(buffer->MountPointReparseBuffer.PathBuffer + target.size() + 1, printname.c_str(), printname_byte_size + 2);
return buffer;
}
typed_buffer_ptr<REPARSE_DATA_BUFFER> BuildSymlink(const std::wstring& target, const std::wstring& printname, bool relative)
{
const size_t target_byte_size = target.size() * 2;
const size_t printname_byte_size = printname.size() * 2;
const size_t path_buffer_size = target_byte_size + printname_byte_size + 12 + 4;
const size_t total_size = path_buffer_size + REPARSE_DATA_BUFFER_HEADER_LENGTH;
typed_buffer_ptr<REPARSE_DATA_BUFFER> buffer(total_size);
buffer->ReparseTag = IO_REPARSE_TAG_SYMLINK;
buffer->ReparseDataLength = static_cast<USHORT>(path_buffer_size);
buffer->Reserved = 0;
buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
buffer->SymbolicLinkReparseBuffer.SubstituteNameLength = static_cast<USHORT>(target_byte_size);
memcpy(buffer->SymbolicLinkReparseBuffer.PathBuffer, target.c_str(), target_byte_size + 2);
buffer->SymbolicLinkReparseBuffer.PrintNameOffset = static_cast<USHORT>(target_byte_size + 2);
buffer->SymbolicLinkReparseBuffer.PrintNameLength = static_cast<USHORT>(printname_byte_size);
memcpy(buffer->SymbolicLinkReparseBuffer.PathBuffer + target.size() + 1, printname.c_str(), printname_byte_size + 2);
buffer->SymbolicLinkReparseBuffer.Flags = relative ? SYMLINK_FLAG_RELATIVE : 0;
return buffer;
}
static bool CreateMountPointInternal(const std::wstring& path, typed_buffer_ptr<REPARSE_DATA_BUFFER>& buffer)
{
ScopedHandle handle = OpenReparsePoint(path, true);
if (!handle.IsValid())
{
return false;
}
return SetReparsePoint(handle, buffer);
}
static bool CreateMountPointInternal(const ScopedHandle& handle, typed_buffer_ptr<REPARSE_DATA_BUFFER>& buffer)
{
return SetReparsePoint(handle, buffer);
}
std::wstring FixupPath(std::wstring str)
{
if (str[0] != '\\')
{
return L"\\??\\" + str;
}
return str;
}
bool ReparsePoint::CreateMountPoint(const std::wstring& path, const std::wstring& target, const std::wstring& printname)
{
if (target.length() == 0)
{
return false;
}
return CreateMountPointInternal(path, BuildMountPoint(FixupPath(target), printname));
}
bool ReparsePoint::CreateSymlink(const std::wstring& path, const std::wstring& target, const std::wstring& printname, bool relative)
{
if (target.length() == 0)
{
return false;
}
return CreateMountPointInternal(path, BuildSymlink(!relative ? FixupPath(target) : target, printname, relative));
}
bool ReparsePoint::CreateSymlink(HANDLE h, const std::wstring& target, const std::wstring& printname, bool relative)
{
ScopedHandle handle(h, true);
if (!handle.IsValid())
{
return false;
}
return CreateMountPointInternal(handle, BuildSymlink(!relative ? FixupPath(target) : target, printname, relative));
}
bool ReparsePoint::DeleteMountPoint(const std::wstring& path)
{
REPARSE_GUID_DATA_BUFFER reparse_buffer = { 0 };
reparse_buffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
ScopedHandle handle = OpenReparsePoint(path, true);
return DeleteReparsePoint(handle, &reparse_buffer);
}
bool ReparsePoint::CreateRawMountPoint(const std::wstring& path, DWORD reparse_tag, const std::vector<BYTE>& buffer)
{
typed_buffer_ptr<REPARSE_DATA_BUFFER> reparse_buffer(8 + buffer.size());
reparse_buffer->ReparseTag = reparse_tag;
reparse_buffer->ReparseDataLength = static_cast<USHORT>(buffer.size());
reparse_buffer->Reserved = 0;
memcpy(reparse_buffer->GenericReparseBuffer.DataBuffer, &buffer[0], buffer.size());
return CreateMountPointInternal(path, reparse_buffer);
}
static typed_buffer_ptr<REPARSE_DATA_BUFFER> GetReparsePointData(ScopedHandle handle)
{
typed_buffer_ptr<REPARSE_DATA_BUFFER> buf(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
DWORD dwBytesReturned;
if (!DeviceIoControl(handle,
FSCTL_GET_REPARSE_POINT,
NULL,
0,
(LPVOID)buf,
buf.size(),
&dwBytesReturned,
0)
)
{
g_last_error = GetLastError();
buf.reset(0);
}
return buf;
}
std::wstring ReparsePoint::GetMountPointTarget(const std::wstring& path)
{
ScopedHandle handle = OpenReparsePoint(path, false);
if (!handle.IsValid())
{
return L"";
}
typed_buffer_ptr<REPARSE_DATA_BUFFER> buf = GetReparsePointData(handle);
if (buf.size() == 0)
{
return L"";
}
if (buf->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
{
g_last_error = ERROR_REPARSE_TAG_MISMATCH;
return L"";
}
WCHAR* base = &buf->MountPointReparseBuffer.PathBuffer[buf->MountPointReparseBuffer.SubstituteNameOffset / 2];
return std::wstring(base, base + (buf->MountPointReparseBuffer.SubstituteNameLength / 2));
}
bool ReparsePoint::IsReparsePoint(const std::wstring& path)
{
ScopedHandle handle = OpenReparsePoint(path, false);
BY_HANDLE_FILE_INFORMATION file_info = { 0 };
return handle.IsValid() && GetFileInformationByHandle(handle, &file_info) && file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT;
}
static bool ReadReparsePoint(const std::wstring& path, typed_buffer_ptr<REPARSE_DATA_BUFFER>& reparse_buffer)
{
ScopedHandle handle = OpenReparsePoint(path, false);
reparse_buffer.reset(4096);
DWORD dwSize;
bool ret = DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nullptr, 0, reparse_buffer, reparse_buffer.size(), &dwSize, nullptr) == TRUE;
if (!ret)
{
g_last_error = GetLastError();
return false;
}
else
{
reparse_buffer.resize(dwSize);
return true;
}
}
static bool IsReparseTag(const std::wstring& path, DWORD reparse_tag)
{
typed_buffer_ptr<REPARSE_DATA_BUFFER> buffer;
if (ReadReparsePoint(path, buffer))
{
return buffer->ReparseTag == reparse_tag;
}
else
{
return false;
}
}
bool ReparsePoint::IsMountPoint(const std::wstring& path)
{
return IsReparseTag(path, IO_REPARSE_TAG_MOUNT_POINT);
}
bool ReparsePoint::IsSymlink(const std::wstring& path)
{
return IsReparseTag(path, IO_REPARSE_TAG_SYMLINK);
}
bool ReparsePoint::ReadMountPoint(const std::wstring& path, std::wstring& target, std::wstring& printname)
{
typed_buffer_ptr<REPARSE_DATA_BUFFER> buffer;
if (ReadReparsePoint(path, buffer) && buffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
WCHAR* target_name = &buffer->MountPointReparseBuffer.PathBuffer[buffer->MountPointReparseBuffer.SubstituteNameOffset / 2];
WCHAR* display_name = &buffer->MountPointReparseBuffer.PathBuffer[buffer->MountPointReparseBuffer.PrintNameOffset / 2];
target.assign(target_name, target_name + buffer->MountPointReparseBuffer.SubstituteNameLength / 2);
printname.assign(display_name, display_name + buffer->MountPointReparseBuffer.PrintNameLength / 2);
return true;
}
else
{
return false;
}
}
bool ReparsePoint::ReadSymlink(const std::wstring& path, std::wstring& target, std::wstring& printname, unsigned int* flags)
{
typed_buffer_ptr<REPARSE_DATA_BUFFER> buffer;
if (ReadReparsePoint(path, buffer) && buffer->ReparseTag == IO_REPARSE_TAG_SYMLINK)
{
WCHAR* target_name = &buffer->SymbolicLinkReparseBuffer.PathBuffer[buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset / 2];
WCHAR* display_name = &buffer->SymbolicLinkReparseBuffer.PathBuffer[buffer->SymbolicLinkReparseBuffer.PrintNameOffset / 2];
target.assign(target_name, target_name + buffer->SymbolicLinkReparseBuffer.SubstituteNameLength / 2);
printname.assign(display_name, display_name + buffer->SymbolicLinkReparseBuffer.PrintNameLength / 2);
*flags = buffer->SymbolicLinkReparseBuffer.Flags;
return true;
}
else
{
return false;
}
}
bool ReparsePoint::ReadRaw(const std::wstring& path, unsigned int* reparse_tag, std::vector<BYTE>& raw_data)
{
typed_buffer_ptr<REPARSE_DATA_BUFFER> buffer;
if (ReadReparsePoint(path, buffer))
{
*reparse_tag = buffer->ReparseTag;
raw_data.resize(buffer->ReparseDataLength);
memcpy(&raw_data[0], buffer->GenericReparseBuffer.DataBuffer, buffer->ReparseDataLength);
return true;
}
else
{
return false;
}
return false;
}