WIP: MISC/mcl_engine_workarounds: Fix malformed client ready packet crashing server #278

Draft
cora wants to merge 1 commits from fix-malformed-clientready into master
Member
Problem

This PR fixes #269

A malformed clientready packet can leave a connecting player indefinietly in a state where they're not fully connected but the player is returned by get_connected_players leading to all kinds of problems.

Solution

This PR sets a meta field in on_joinplayer and overwrites get_connected_players to check for that.

Testing Steps

1.Start a game in mcla master with host game
2. run the crash script on localhost:30000
3. Observe the game crash
4. Start a game on this pr
5. run the crash script on localhost:30000
6. Observe the game not crash

To do
  • Find out if any get_connected_players are run in other on_joinplayers and adapt those (if any exist).
##### Problem This PR fixes #269 A malformed clientready packet can leave a connecting player indefinietly in a state where they're not fully connected but the player is returned by get_connected_players leading to all kinds of problems. ##### Solution This PR sets a meta field in on_joinplayer and overwrites get_connected_players to check for that. ##### Testing Steps 1.Start a game in mcla master with host game 2. run the crash script on localhost:30000 3. Observe the game crash 4. Start a game on this pr 5. run the crash script on localhost:30000 6. Observe the game not crash ##### To do - [ ] Find out if any get_connected_players are run in other on_joinplayers and adapt those (if any exist).
cora added 1 commit 2022-02-15 04:10:50 +01:00
Author
Member

Another way to do this would of course be just @erlehmann's original solution: cache all players in on_joinplayer and just return that table(nothing else needed in theory). I don't know if the references stay good throughout the entire connection though so I thought this way might be smarter.

The method of detection is that the "fake" clients do not cause on_joinplayer to be run in any case.

Another way to do this would of course be just @erlehmann's original solution: cache all players in on_joinplayer and just return that table(nothing else needed in theory). I don't know if the references stay good throughout the entire connection though so I thought this way might be smarter. The method of detection is that the "fake" clients do not cause on_joinplayer to be run in any case.
First-time contributor

It seems it doesn't work.
What you fix - only players connected. But they still appear as dummy objects like this:
image
They take part in all interactions and crash the servers successfully because many places in the code aren't ready for that.

It seems it doesn't work. What you fix - only players connected. But they still appear as dummy objects like this: ![image](/attachments/08b176db-ed6e-43cf-8ae0-018096ac2940) They take part in all interactions and crash the servers successfully because many places in the code aren't ready for that.
258 KiB
Author
Member

Well thats the least intrusive way i could think of that doesnt crash the game. If you don't want them you can easily compare original mgcp with the cool new one and kick them when theyre not in the latter ^^

Well thats the least intrusive way i could think of that doesnt crash the game. If you don't want them you can easily compare original mgcp with the cool new one and kick them when theyre not in the latter ^^
Author
Member

They take part in all interactions and crash the servers successfully because many places in the code aren't ready for that.

Maybe you're right. I'm not sure where else the player object might come from though if not from mgcp or on_joinplayer.

But i suppose it's possible.

> They take part in all interactions and crash the servers successfully because many places in the code aren't ready for that. Maybe you're right. I'm not sure where else the player object might come from though if not from mgcp or on_joinplayer. But i suppose it's possible.
Author
Member

Did it actually crash for you though ?

Did it actually crash for you though ?
First-time contributor

I can't disconnect/cick them and they crashed the server on interactions, last stuff I tried to fix has been logged when they magneted xp orbs

I added some info to the minetest issue but still think we should better consider them in main loops, it looks simpler

I can't disconnect/cick them and they crashed the server on interactions, last stuff I tried to fix has been logged when they magneted xp orbs I added some info to the minetest issue but still think we should better consider them in main loops, it looks simpler
Author
Member

I can't disconnect/cick them and they crashed the server on interactions, last stuff I tried to fix has been logged when they magneted xp orbs

I added some info to the minetest issue but still think we should better consider them in main loops, it looks simpler

Oh yeah i see. I'll try to figure it out.

> I can't disconnect/cick them and they crashed the server on interactions, last stuff I tried to fix has been logged when they magneted xp orbs > > I added some info to the minetest issue but still think we should better consider them in main loops, it looks simpler Oh yeah i see. I'll try to figure it out.
First-time contributor

To be honest, my problem is - I can't run the crashing script and go to sleep, though it could help to locate and fix many places of code. Locally, I can but almost nothing is happening locally, so I think, wow, everything is fixed, run crash.py for real server, it works 5-10 minutes and wow - we've got something new

To be honest, my problem is - I can't run the crashing script and go to sleep, though it could help to locate and fix many places of code. Locally, I can but almost nothing is happening locally, so I think, wow, everything is fixed, run crash.py for real server, it works 5-10 minutes and wow - we've got something new
Author
Member

No I think if they actually cause widespread weirdness we can't let them connect.

Also i'm dumb ... yeah portals would also make it crash possibly cause they search for objects in the area right?

I think it might be good to detect and rewrite the is_player method of the fake player. That should take care of the rest. They aren't a problem bc theyre objects but bc they're players right ?

No I think if they actually cause widespread weirdness we can't let them connect. Also i'm dumb ... yeah portals would also make it crash possibly cause they search for objects in the area right? I think it might be good to detect and rewrite the is_player method of the fake player. That should take care of the rest. They aren't a problem bc theyre objects but bc they're players right ?
First-time contributor

The only problem, I think, we didn't initialize them because they didn't go through the callback where we used to initialize player stuff. Some oldschool practice says, deal only player names, don't deal player objects, but when you need player meta, or inventory, or texture, entities, effects - it is not enough to process just names. So yes, we have to create player object somehow (which is more likely impossible for Lua) or ignore them in all loops.

mcl_util.is_player in testing branch of mcl5 right now:


local possible_hackers = {}

function mcl_util.is_player(obj)
	if not obj then return end
	if not obj.is_player then return end
	if not obj:is_player() then return end
	local name = obj:get_player_name()
	if not name then return end
	if possible_hackers[name] then return end
	return true
end

minetest.register_on_authplayer(function(name, ip, is_success)
	if not is_success then return end
	possible_hackers[name] = true
end)

minetest.register_on_joinplayer(function(player)
	possible_hackers[player:get_player_name()] = nil
end)

I just replace if dropper:is_player() to if mcl_util and mcl_util.is_player(dropper), etc.

Why mcl_util and - because we have wrong dependencies currently which is another issue

The only problem, I think, we didn't initialize them because they didn't go through the callback where we used to initialize player stuff. Some oldschool practice says, deal only player names, don't deal player objects, but when you need player meta, or inventory, or texture, entities, effects - it is not enough to process just names. So yes, we have to create player object somehow (which is more likely impossible for Lua) or ignore them in all loops. mcl_util.is_player in testing branch of mcl5 right now: ```lua local possible_hackers = {} function mcl_util.is_player(obj) if not obj then return end if not obj.is_player then return end if not obj:is_player() then return end local name = obj:get_player_name() if not name then return end if possible_hackers[name] then return end return true end minetest.register_on_authplayer(function(name, ip, is_success) if not is_success then return end possible_hackers[name] = true end) minetest.register_on_joinplayer(function(player) possible_hackers[player:get_player_name()] = nil end) ``` I just replace `if dropper:is_player()` to `if mcl_util and mcl_util.is_player(dropper)`, etc. Why `mcl_util and` - because we have wrong dependencies currently which is another issue
Author
Member

Well i meant make it just return false. If we detect one of these. Then we can probably even let them stay because i suppose if theyre not seen as players by mcl they won't be a problem.

I haven't figured out a great method to detect them though other than running mgcp in a globalstep and checking that.

Another method i thought about is having a timer start in prejoinplayer and if joinplayer doesnt happen within say 2 seconds we rewrite the is_player function.

Are you sure they can't be kicked though? kick takes a name as arg right ?

Well i meant make it just return false. If we detect one of these. Then we can probably even let them stay because i suppose if theyre not seen as players by mcl they won't be a problem. I haven't figured out a great method to detect them though other than running mgcp in a globalstep and checking that. Another method i thought about is having a timer start in prejoinplayer and if joinplayer doesnt happen within say 2 seconds we rewrite the is_player function. Are you sure they can't be kicked though? kick takes a name as arg right ?
First-time contributor

yea, have no idea why, tried lot of times, my latest attempt to make it work is a mess but you will see what i tried to do from it

diff --git a/mods/CORE/mcl_init/init.lua b/mods/CORE/mcl_init/init.lua
index f66cbf2d9..966967053 100644
--- a/mods/CORE/mcl_init/init.lua
+++ b/mods/CORE/mcl_init/init.lua
@@ -36,28 +36,29 @@ math.randomseed(os.time())
 local player_list = {}
 local player_hash = {}
 local is_player_list_outdated = true
-minetest.register_on_joinplayer(function(player)
-	player_hash[player:get_player_name()] = player
+minetest.register_on_joinplayer(function(player_object)
+	player_hash[player_object:get_player_name()] = player_object
 	is_player_list_outdated = true
 end)
-minetest.register_on_leaveplayer(function(player)
-	player_hash[player:get_player_name()] = nil
+minetest.register_on_leaveplayer(function(player_object)
+	player_hash[player_object:get_player_name()] = nil
 	is_player_list_outdated = true
 end)
 function minetest.get_connected_players()
 	if is_player_list_outdated then
 		player_list = {}
-		for _, value in pairs(player_hash) do
-			player_list[#player_list + 1] = value
+		for _, player_object in pairs(player_hash) do
+			player_list[#player_list + 1] = player_object
 		end
 		is_player_list_outdated = false
 	end
 	return player_list
 end
-minetest.register_globalstep(function()
-	for _, pl in pairs(core.get_connected_players()) do
-		if not player_hash[pl:get_player_name()] then
-			minetest.disconnect_player(pl:get_player_name(), "It was all a dream.")
+minetest.register_on_authplayer(function(name, ip, is_success)
+	minetest.after(0.01, function(name)
+		if not player_hash[name] then
+			minetest.chat_send_all("Disconnecting " .. tostring(ip) .. " for server crashing attempt")
+			minetest.kick_player(name, "It was all a dream.")
 		end
-	end
+	end)
 end)
yea, have no idea why, tried lot of times, my latest attempt to make it work is a mess but you will see what i tried to do from it ```diff diff --git a/mods/CORE/mcl_init/init.lua b/mods/CORE/mcl_init/init.lua index f66cbf2d9..966967053 100644 --- a/mods/CORE/mcl_init/init.lua +++ b/mods/CORE/mcl_init/init.lua @@ -36,28 +36,29 @@ math.randomseed(os.time()) local player_list = {} local player_hash = {} local is_player_list_outdated = true -minetest.register_on_joinplayer(function(player) - player_hash[player:get_player_name()] = player +minetest.register_on_joinplayer(function(player_object) + player_hash[player_object:get_player_name()] = player_object is_player_list_outdated = true end) -minetest.register_on_leaveplayer(function(player) - player_hash[player:get_player_name()] = nil +minetest.register_on_leaveplayer(function(player_object) + player_hash[player_object:get_player_name()] = nil is_player_list_outdated = true end) function minetest.get_connected_players() if is_player_list_outdated then player_list = {} - for _, value in pairs(player_hash) do - player_list[#player_list + 1] = value + for _, player_object in pairs(player_hash) do + player_list[#player_list + 1] = player_object end is_player_list_outdated = false end return player_list end -minetest.register_globalstep(function() - for _, pl in pairs(core.get_connected_players()) do - if not player_hash[pl:get_player_name()] then - minetest.disconnect_player(pl:get_player_name(), "It was all a dream.") +minetest.register_on_authplayer(function(name, ip, is_success) + minetest.after(0.01, function(name) + if not player_hash[name] then + minetest.chat_send_all("Disconnecting " .. tostring(ip) .. " for server crashing attempt") + minetest.kick_player(name, "It was all a dream.") end - end + end) end) ```
Author
Member

mmmh i wonder .. can we get the object through get_player_by_name ?

mmmh i wonder .. can we get the object through get_player_by_name ?
Author
Member

mmmh this seems to work for me (p similar to yours ig)

local sus={}
minetest.register_on_prejoinplayer(function(name) 
    sus[name]=true
    minetest.after(1,function()
	if not sus[name] then return end
        minetest.chat_send_all(name.." is sus")
	    minetest.kick_player(name)
    end)
end)
minetest.register_on_joinplayer(function(p)
    local name=p:get_player_name()
    sus[name]=nil
end)
mmmh this seems to work for me (p similar to yours ig) ```lua local sus={} minetest.register_on_prejoinplayer(function(name) sus[name]=true minetest.after(1,function() if not sus[name] then return end minetest.chat_send_all(name.." is sus") minetest.kick_player(name) end) end) minetest.register_on_joinplayer(function(p) local name=p:get_player_name() sus[name]=nil end) ```
Author
Member

The problem is that media dl might well take longer i suppose

The problem is that media dl might well take longer i suppose
First-time contributor

Well, on auth they are NIL:
image

Well, on auth they are NIL: ![image](/attachments/21df2e77-0840-438c-bcce-ea786df8b06f)
653 KiB
First-time contributor

after 2 seconds of being idle - still nil

after 2 seconds of being idle - still `nil`
First-time contributor

This doesn't work too, predictably

minetest.register_on_authplayer(function(name, ip, is_success)
	minetest.after(2, function()
		local connected_players = minetest.get_connected_players()
		for _, player in pairs(connected_players) do
			local player_name = player:get_player_name()
			if name == player_name then
				minetest.chat_send_all("Found an object: "..tostring(player))
			end
		end
	end)
end)
This doesn't work too, predictably ```lua minetest.register_on_authplayer(function(name, ip, is_success) minetest.after(2, function() local connected_players = minetest.get_connected_players() for _, player in pairs(connected_players) do local player_name = player:get_player_name() if name == player_name then minetest.chat_send_all("Found an object: "..tostring(player)) end end end) end) ```
Author
Member

the thing that seems to work is that they do not trigger on_joinplayer.

the thing that seems to work is that they do not trigger on_joinplayer.
This repo is archived. You cannot comment on pull requests.
No description provided.