sourcemod/core/pb_handle.h
Peace-Maker 15450a6d0c Fix use-after-free when creating custom user messages
When creating our own "owned and local" protobuf message in `StartProtobufMessage`, `m_FakeEngineBuffer` is used to track that message. In `EndMessage` the message is optionally converted to a "private" one with the right abi on osx and passed to the engine's `SendUserMessage`. On linux and windows the same message as in the `m_FakeEngineBuffer` is passed though without conversion. `engine->SendUserMessage` has a vtable hook which sets `m_FakeEngineBuffer` to the passed argument.

`m_FakeEngineBuffer` frees the message it previously held, since it's "owned" from `StartProtobufMessage`, but that's the same one that's passed in as argument so a use-after-free in the engine happens when the now-freed message pointer is forwarded to the real `SendUserMessage` in the engine.

The message created in `StartProtobufMessage` wasn't free'd at all when hooks are blocked too. This fix moves the message buffer into a local variable which is destroyed at the end of the function.

Fixes #1286 and #1296
2020-06-23 10:32:55 -07:00

218 lines
6.5 KiB
C++

/**
* vim: set ts=4 sw=4 tw=99 noet :
* =============================================================================
* SourceMod
* Copyright (C) 2020 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#pragma once
#if defined(PROTOBUF_ENABLE)
#include <google/protobuf/message.h>
#if defined(PROTOBUF_PROXY_ENABLE)
# include "pb_proxy.h"
extern IProtobufProxy* gProtobufProxy;
#endif
const google::protobuf::Message *GetMessagePrototype(int msg_type);
// On SDKs which use protobufs, the engine has objects compiled against a specific
// version of protobuf. Normally this is fine, we take care on Linux to use the
// same C++ ABI. On macOS however, we use libc++ to enable C++11 functionality,
// whereas the protobuf library has been compiled with libstc++. These ABIs are
// not compatible.
//
// To address the problem, we introduce PbHandle. PbHandle is a wrapper around
// protobuf::Message with two added pieces of state: whether or not the handle
// "owns" the message (and can free it in its destructor), and whether or not
// the handle was created by the engine (private) or created by SourceMod
// (local).
//
// Whenever we transfer a protobuf::Message pointer to SourceMod, we must take
// care to convert it to a Local version first. Whenever we transfer a protobuf
// pointer to the engine, we must convert it to a Private handle.
//
// For platforms with no ABI differences (almost all of them), the handle is a
// no-op. The private and local localities are compatible and no translation
// takes place.
//
// On macOS, CS:GO does require translation. SourceMod loads a tiny shim
// library that contains a copy of the protobuf sources compiled against the
// game's ABI. It then provides serialization and deserialization methods.
// SourceMod must not interact with the game's protobuf objects without first
// going through this proxy library.
//
// Note that PbHandle is not quite like unique_ptr_. It can be converted into a
// PbHandle that does not destroy the underlying object. This is mainly because
// UserMessages.cpp has rather complex state, so it is useful to track locality
// without destroying an object. An unowned PbHandle must not outlive the
// owning PbHandle.
class PbHandle
{
public:
enum Ownership {
Owned,
Unowned,
};
enum Locality {
Local,
Private,
};
PbHandle() : msg_(nullptr) {}
PbHandle(decltype(nullptr)) : msg_(nullptr) {}
PbHandle(google::protobuf::Message* msg, Ownership ownership, Locality locality)
: msg_(msg),
ownership_(ownership),
locality_(locality)
{}
PbHandle(PbHandle&& other)
: msg_(other.msg_),
ownership_(other.ownership_),
locality_(other.locality_)
{
other.msg_ = nullptr;
}
PbHandle(const PbHandle&) = delete;
~PbHandle() {
maybe_free();
}
PbHandle& operator =(PbHandle&& other) {
if (other.msg_ != msg_)
maybe_free();
msg_ = other.msg_;
ownership_ = other.ownership_;
locality_ = other.locality_;
other.msg_ = nullptr;
return *this;
}
PbHandle& operator =(const PbHandle&) = delete;
google::protobuf::Message* operator ->() const { return msg_; }
google::protobuf::Message* GetLocalMessage() {
assert(locality_ == Local);
return msg_;
}
google::protobuf::Message* GetPrivateMessage() {
assert(locality_ == Private);
return msg_;
}
PbHandle ToLocal(int msg_type) {
if (locality_ == Local)
return AsUnowned();
#if defined(PROTOBUF_PROXY_ENABLE)
PbHandle local(GetMessagePrototype(msg_type)->New(), Owned, Local);
local.CopyFromPrivate(msg_);
return local;
#else
return PbHandle(msg_, Unowned, Local);
#endif
}
PbHandle ToPrivate(int msg_type) {
if (locality_ == Private)
return AsUnowned();
#if defined(PROTOBUF_PROXY_ENABLE)
PbHandle priv(gProtobufProxy->NewPrototype(msg_type), Owned, Private);
priv.CopyFromLocal(msg_);
return priv;
#else
return PbHandle(msg_, Unowned, Private);
#endif
}
void CopyFrom(const PbHandle& other) {
if (other.msg_ == msg_) {
assert(other.locality_ == locality_);
return;
}
#if defined(PROTOBUF_PROXY_ENABLE)
if (other.locality_ == Local)
CopyFromLocal(other.msg_);
else if (other.locality_ == Private)
CopyFromPrivate(other.msg_);
#else
msg_->CopyFrom(*other.msg_);
#endif
}
PbHandle AsUnowned() {
return PbHandle(msg_, Unowned, locality_);
}
bool is_owned() const { return ownership_ == Owned; }
bool is_local() const { return locality_ == Local; }
private:
#if defined(PROTOBUF_PROXY_ENABLE)
void CopyFromPrivate(google::protobuf::Message* message) {
void* out;
size_t len;
if (!gProtobufProxy->Serialize(message, &out, &len))
return;
if (locality_ == Local)
msg_->ParsePartialFromArray(out, len);
else
gProtobufProxy->Deserialize(out, len, msg_);
gProtobufProxy->FreeBuffer(out);
}
void CopyFromLocal(google::protobuf::Message* message) {
if (locality_ == Local) {
msg_->CopyFrom(*message);
} else {
auto data = message->SerializePartialAsString();
gProtobufProxy->Deserialize(data.data(), data.size(), msg_);
}
}
#endif
void maybe_free() {
if (ownership_ != Owned)
return;
#if defined(PROTOBUF_PROXY_ENABLE)
if (locality_ == Private) {
gProtobufProxy->FreeMessage(msg_);
return;
}
#endif
delete msg_;
}
private:
google::protobuf::Message* msg_ = nullptr;
Ownership ownership_ = Unowned;
Locality locality_ = Local;
};
#endif // PROTOBUF_ENABLE