Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State bags set on the server do not respect replicated flag #2438

Open
jxckUK opened this issue Mar 27, 2024 · 6 comments · May be fixed by #2445
Open

State bags set on the server do not respect replicated flag #2438

jxckUK opened this issue Mar 27, 2024 · 6 comments · May be fixed by #2445
Labels
bug triage Needs a preliminary assessment to determine the urgency and required action

Comments

@jxckUK
Copy link

jxckUK commented Mar 27, 2024

What happened?

As reported originally back in 2021 by @AvarianKnight here https://forum.cfx.re/t/server-side-entity-state-bags-sync-when-replicate-is-false/2313177 and seems it was never opened here.

I ran into this issue and did some research and found the original report.

I would argue that this is a security issue because currently when scripting developers are setting this value to be false there could be an expectation that its working correctly, therefore data stored with the intent to not replicate in fact is replicating which would be a data security issue.

Expected result

It should respect the replicated flag being set to false and not replicate the state to the clients.

Reproduction steps

  1. Create an entity or player state server side and set the replicated flag to false.
  2. Query the state client side and see that the data is replicated to other players when entering their scope.

There is also a reproduction script in @AvarianKnight's original report.

Importancy

Security issue

Area(s)

OneSync

Specific version(s)

FiveM, Server 7752, Linux

Additional information

No response

@jxckUK jxckUK added bug triage Needs a preliminary assessment to determine the urgency and required action labels Mar 27, 2024
@tens0rfl0w
Copy link
Contributor

tens0rfl0w commented Mar 27, 2024

Simply using:

Player(source).state:set("test", "hi", false)

works as expected and does not replicate the state bag value to any client. (Same for entity state bags)

So there have to be other factors to make this not work as expected.

@AvarianKnight
Copy link
Contributor

AvarianKnight commented Mar 27, 2024

iirc this requires you to send a replicated value afterwards and/or re-enter the entities/player scope.

The underlying implementation for state bags doesn't have anything to respect the replication flag sent by the caller after the first call

fx::ScriptEngine::RegisterNativeHandler("SET_STATE_BAG_VALUE", [](fx::ScriptContext& context)
{
auto bagName = context.CheckArgument<const char*>(0);
auto keyName = context.CheckArgument<const char*>(1);
auto keyValue = context.CheckArgument<const char*>(2);
auto keySize = context.GetArgument<uint32_t>(3);
auto replicated = context.GetArgument<bool>(4);
auto rm = fx::ResourceManager::GetCurrent();
auto sbac = rm->GetComponent<fx::StateBagComponent>();
if (auto bag = sbac->GetStateBag(bagName); bag)
{
bag->SetKey(0, keyName, std::string_view{ keyValue, keySize }, replicated);
context.SetResult(true);
return;
}
context.SetResult(false);
});

auto continuation = [thisRef, source, replicated](std::string_view key, std::string_view data)
{
static_cast<StateBagImpl*>(thisRef.get())->SetKeyInternal(source, key, data, replicated);
};

void StateBagImpl::SetKeyInternal(int source, std::string_view key, std::string_view data, bool replicated)
{
{
std::unique_lock _(m_dataMutex);
if (auto it = m_data.find(key); it != m_data.end())
{
if (data != it->second)
{
it->second = data;
}
else
{
replicated = false; // nothing changed
}
}
else
{
m_data.emplace(key, data);
}
}
if (replicated)
{
if (m_replicationEnabled)
{
SendKeyValueToAllTargets(key, data);
}
else
{
// store it for now
std::unique_lock lk(m_replicateDataMutex);
m_replicateData.emplace(key, data);
}
}
}

@tens0rfl0w
Copy link
Contributor

tens0rfl0w commented Mar 27, 2024

Looking at @AvarianKnight's repro from the initial issue:
Replication state does seem to get ignored when calling a 'set' operation on entities too fast after init creation.

Simply waiting a few ticks after entity creation correctly applies the replication state to the state bag key.

For example, waiting 100 ticks after calling CreateAutomobile and another 100 ticks after the two 'set' operations then correctly replicates the state bag values: (second wait is just to ensure values got synced to the client)
image

(This also works if setting a replicated state bag key on the same entity afterwards.)

@AvarianKnight
Copy link
Contributor

This ties into the original issue of

The underlying implementation for state bags doesn't have anything to respect the replication flag sent by the caller after the first call

When the state bag is initially created it doesn't send the updates to anyone until the client has said that it has the entity created, this will call AddRoutingTarget which will sync all the key/values to the client (without respect for the replication flag)

if (syncData.hasCreated)
{
// Add this player as a routing target to this entity's statebag, if present.
// notes:
// * this will try to add it every frame, but the statebag will only add it once (std::set).
// * will occur on the next update/tick when syncData.hasCreated is true, this'll ensure that it's sent after the client knows about this entity.
// TODO: PERF: remove this every-frame call by giving this system a nice and fresh design
if (auto stateBag = entity->GetStateBag())
{
stateBag->AddRoutingTarget(slotId);
}
}

@tens0rfl0w
Copy link
Contributor

Verified exactly what you just described:
Replication state is only applied when the client is in scope of the entity/the entity already exists on the client before the 'set' operation is executed.👍🏼Never noticed that before.

@jxckUK
Copy link
Author

jxckUK commented Mar 27, 2024

Thanks both for the much more detailed insight into the issue than I would have been able to provide!

and/or re-enter the entities/player scope.

This particular issue is the one that alerted me to the problem, delaying the set operation in this instance won't provide a valid mitigation (at least for Player state) because the players within a users scope is subject to change at any time.

I assume this doesn't affect states set on the client side which are set to not replicate by default? Or are they also affected by the underlying implementation not respecting replication flags post creation/first call?

tens0rfl0w added a commit to tens0rfl0w/fivem that referenced this issue Apr 2, 2024
* Previously, we didn't keep track of the replication state of a sbag key, resulting in sending all sbag data when a client entered the scope of an entity/player (regardless of the replication state).
* We now store the replication state of a sbag key and check if we should replicate its data when sending all sbag data to the client.

Fixes citizenfx#2438
tens0rfl0w added a commit to tens0rfl0w/fivem that referenced this issue Apr 2, 2024
* Previously, we didn't keep track of the replication state of a sbag key, resulting in sending all sbag data when a client entered the scope of an entity/player (regardless of the replication state).
* We now store the replication state of a sbag key and check if we should replicate its data when sending all sbag data to the client.

Fixes citizenfx#2438
@tens0rfl0w tens0rfl0w linked a pull request Apr 2, 2024 that will close this issue
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug triage Needs a preliminary assessment to determine the urgency and required action
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants