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

Improve sandboxing setup; re-allow get/setmetatable #245

Merged
merged 5 commits into from
Aug 2, 2024

Conversation

GinjaNinja32
Copy link
Contributor

  • Give each script its own proxy for global tables; this allows scripts to set e.g. string.foo = ... without affecting other scripts' view of the library.
  • Protect all important metatables for values provided by C++
  • Remove getmetatable and setmetatable from the list of disallowed functions

- Give each script its own proxy for global tables; this allows scripts
  to set e.g. `string.foo = ...` without affecting other scripts' view
  of the library.
- Protect all important metatables for values provided by C++
- Remove getmetatable and setmetatable from the list of disallowed
  functions
@GinjaNinja32
Copy link
Contributor Author

a thing I wrote to stress-test this and search for issues:

-- Sandbox breakout test

local IDENTS = {
	-- Base Lua
	"_G", "_VERSION", "arg", "assert", "collectgarbage", "coroutine", "debug", "dofile",
	"error", "getmetatable", "io", "ipairs", "load", "loadfile", "math", "next", "os",
	"package", "pairs", "pcall", "print", "rawequal", "rawget", "rawlen", "rawset",
	"require", "select", "setmetatable", "string", "table", "tonumber", "tostring",
	"type", "utf8", "warn", "xpcall",

	-- SeriousProton
	"random", "irandom", "traceback",

	-- EmptyEpsilon
	"_", "addBeamPosition", "addBroadcast", "addCommsReply", "addCustomButton", "addCustomInfo", "addCustomMessage", "addCustomMessageWithCallback", "addDoor", "addEngineEmitor", "addEngineEmitter", "addEntry", "addGMFunction", "addGMMessage", "addKeyValue", "addReputationPoints", "addRoom", "addRoomSystem", "addToShipLog", "addTubePosition", "allowNewPlayerShips", "allowPickup", "areBeamShieldFrequenciesUsed", "areEnemiesInRange", "Artifact", "Asteroid", "BeamEffect", "BlackHole", "clearGMFunctions", "Collisionable", "commandAbortDock", "commandActivateSelfDestruct", "commandAddWaypoint", "commandAnswerCommHail", "commandCancelSelfDestruct", "commandClearScienceLink", "commandCloseTextComm", "commandCombatManeuverBoost", "commandConfirmDestructCode", "commandDock", "commandFireTube", "commandFireTubeAtTarget", "commandImpulse", "commandJump", "commandLaunchProbe", "commandLoadTube", "commandMainScreenOverlay", "commandMainScreenSetting", "commandMoveWaypoint", "commandOpenTextComm", "commandRemoveWaypoint", "commandScan", "commandSendComm", "commandSendCommPlayer", "commandSetAlertLevel", "commandSetAutoRepair", "commandSetBeamFrequency", "commandSetBeamSystemTarget", "commandSetScienceLink", "commandSetShieldFrequency", "commandSetShields", "commandSetSystemCoolantRequest", "commandSetSystemPowerRequest", "commandSetTarget", "commandTargetRotation", "commandUndock", "commandUnloadTube", "commandWarp", "commsSwitchToGM", "copy", "CpuShip", "ElectricExplosionEffect", "EMPMissile", "explode", "ExplosionEffect", "FactionInfo", "finish", "get", "getAcceleration", "getActivePlayerShips", "getAlertLevel", "getAllObjects", "getAngularVelocity", "getBeamFrequency", "getBeamSystemTarget", "getBeamSystemTargetName", "getBeamWeaponArc", "getBeamWeaponCycleTime", "getBeamWeaponDamage", "getBeamWeaponDirection", "getBeamWeaponEnergyPerFire", "getBeamWeaponHeatPerFire", "getBeamWeaponRange", "getBeamWeaponTurretArc", "getBeamWeaponTurretDirection", "getCallSign", "getCanBeDestroyed", "getCanCombatManeuver", "getCanDock", "getCanHack", "getCanLaunchProbe", "getCanScan", "getCanSelfDestruct", "getCollisionSize", "getDescription", "getDockedWith", "getDockingState", "getDynamicRadarSignatureBiological", "getDynamicRadarSignatureElectrical", "getDynamicRadarSignatureGravity", "getEEVersion", "getEnergy", "getEnergyLevel", "getEnergyLevelMax", "getEnergyShieldUsePerSecond", "getEnergyWarpPerSecond", "getEntries", "getEntryByName", "getFaction", "getFactionId", "getFactionInfo", "getFrontShield", "getFrontShieldMax", "getGameLanguage", "getGMSelection", "getHackingDifficulty", "getHackingGames", "getHeading", "getHull", "getHullMax", "getId", "getImage", "getImpulseMaxSpeed", "getJumpDelay", "getJumpDriveCharge", "getKeyValue", "getKeyValues", "getLabel", "getLifetime", "getLocaleFaction", "getLongDescription", "getLongRangeRadarRange", "getMaxCoolant", "getMaxEnergy", "getMaxScanProbeCount", "getMissileSize", "getName", "getObjectsInRadius", "getObjectsInRange", "getOrder", "getOrderTarget", "getOrderTargetLocation", "getOwner", "getParentId", "getPlanetRadius", "getPlayerShip", "getPosition", "getRadarSignatureBiological", "getRadarSignatureElectrical", "getRadarSignatureGravity", "getRange", "getRearShield", "getRearShieldMax", "getRepairCrewCount", "getRepairDocked", "getReputationPoints", "getRestocksMissilesDocked", "getRestocksScanProbes", "getRotation", "getRotationMaxSpeed", "getScanningComplexity", "getScanProbeCount", "getScenarioSetting", "getScenarioTime", "getScenarioVariation", "getScienceDatabases", "getScriptStorage", "getSectorName", "getSelfDestructDamage", "getSelfDestructSize", "getSharesEnergyWithDocked", "getShieldCount", "getShieldLevel", "getShieldMax", "getShieldsActive", "getShieldsFrequency", "getShortRangeRadarRange", "getSize", "getSpeed", "getSystemCoolant", "getSystemCoolantRate", "getSystemHackedLevel", "getSystemHealth", "getSystemHealthMax", "getSystemHeat", "getSystemHeatRate", "getSystemPower", "getSystemPowerFactor", "getSystemPowerRate", "getTarget", "getTargetPosition", "getTemplate", "getTubeLoadTime", "getTubeSize", "getTypeName", "getVelocity", "getWarpSpeed", "getWaypoint", "getWaypointCount", "getWeaponStorage", "getWeaponStorageMax", "getWeaponTubeCount", "getWeaponTubeLoadType", "globalMessage", "hasEntries", "hasJumpDrive", "hasPlayerAtPosition", "hasSystem", "hasWarpDrive", "hidden", "HomingMissile", "HVLI", "isCommsBeingHailed", "isCommsBeingHailedByGM", "isCommsBroken", "isCommsChatOpen", "isCommsChatOpenToGM", "isCommsChatOpenToPlayer", "isCommsClosed", "isCommsFailed", "isCommsInactive", "isCommsOpening", "isCommsScriptOpen", "isDocked", "isEnemy", "isFriendly", "isFriendOrFoeIdentified", "isFriendOrFoeIdentifiedBy", "isFriendOrFoeIdentifiedByFaction", "isFullyScanned", "isFullyScannedBy", "isFullyScannedByFaction", "isInside", "isLongRangeRadarAllowed", "isPerSystemDamageUsed", "isScanned", "isScannedBy", "isScannedByFaction", "isTacticalRadarAllowed", "log", "Mine", "MissileWeapon", "ModelData", "Nebula", "Nuke", "onArrival", "onCollision", "onDestroyed", "onDestruction", "onExpiration", "onGMClick", "onNewPlayerShip", "onNext", "onPickup", "onPickUp", "onPlayerCollision", "onProbeLaunch", "onProbeLink", "onProbeUnlink", "onTakingDamage", "onTeleportation", "openCommsTo", "orderAttack", "orderDefendLocation", "orderDefendTarget", "orderDock", "orderFlyFormation", "orderFlyTowards", "orderFlyTowardsBlind", "orderIdle", "orderRetreat", "orderRoaming", "orderStandGround", "pauseGame", "Planet", "PlayerSpaceship", "playSoundFile", "print", "queryScienceDatabase", "queryScienceDatabaseById", "removeCustom", "removeGMFunction", "removeKey", "require", "run", "scanningChannelDepth", "scanningComplexity", "ScanProbe", "ScienceDatabase", "Script", "ScriptObject", "ScriptStorage", "sectorToXY", "sendCommsMessage", "sendCommsMessageNoLog", "set", "setAcceleration", "setAI", "setAngularVelocity", "setAutoCoolant", "setAxialRotationTime", "setBanner", "setBeam", "setBeamFireSound", "setBeamFireSoundPower", "setBeamTexture", "setBeamWeapon", "setBeamWeaponArcColor", "setBeamWeaponDamageType", "setBeamWeaponEnergyPerFire", "setBeamWeaponHeatPerFire", "setBeamWeaponTexture", "setBeamWeaponTurret", "setCallSign", "setCanBeDestroyed", "setCanCombatManeuver", "setCanDock", "setCanHack", "setCanLaunchProbe", "setCanScan", "setCanSelfDestruct", "setClass", "setCloaking", "setCollisionBox", "setColor", "setCombatManeuver", "setCommsFunction", "setCommsMessage", "setCommsScript", "setControlCode", "setDefaultAI", "setDescription", "setDescriptionForScanState", "setDescriptions", "setDistanceFromMovementPlane", "setDockClasses", "setDuration", "setEnemy", "setEnergy", "setEnergyLevel", "setEnergyLevelMax", "setEnergyShieldUsePerSecond", "setEnergyStorage", "setEnergyWarpPerSecond", "setExternalDockClasses", "setFaction", "setFactionId", "setFriendly", "setFrontShield", "setFrontShieldMax", "setGMColor", "setHeading", "setHull", "setHullMax", "setIllumination", "setImage", "setImpulseMaxSpeed", "setImpulseSoundFile", "setInternalDockClasses", "setJumpDrive", "setJumpDriveCharge", "setJumpDriveRange", "setKeyValue", "setLabel", "setLifetime", "setLocaleName", "setLongDescription", "setLongRangeRadarRange", "setMaxCoolant", "setMaxEnergy", "setMaxScanProbeCount", "setMesh", "setMessageToBottomPosition", "setMessageToTopPosition", "setMissileSize", "setModel", "setModelDataName", "setName", "setNeutral", "setOnRadar", "setOrbit", "setOwner", "setPlanetAtmosphereColor", "setPlanetAtmosphereTexture", "setPlanetCloudRadius", "setPlanetCloudTexture", "setPlanetRadius", "setPlanetSurfaceTexture", "setPlayerShip", "setPoints", "setPosition", "setRadarSignatureInfo", "setRadarTrace", "setRadarTraceColor", "setRadarTraceIcon", "setRadarTraceScale", "setRadius", "setRange", "setRearShield", "setRearShieldMax", "setRenderOffset", "setRepairCrewCount", "setRepairDocked", "setReputationPoints", "setRestocksMissilesDocked", "setRestocksScanProbes", "setRing", "setRotation", "setRotationMaxSpeed", "setScale", "setScanned", "setScannedByFaction", "setScanningParameters", "setScanProbeCount", "setScanState", "setScanStateByFaction", "setScenario", "setSelfDestructDamage", "setSelfDestructSize", "setSharesEnergyWithDocked", "setShields", "setShieldsActive", "setShieldsFrequency", "setShieldsMax", "setShipTemplate", "setShortRangeRadarRange", "setSize", "setSource", "setSpecular", "setSpeed", "setSpin", "setSystemCoolant", "setSystemCoolantRate", "setSystemHackedLevel", "setSystemHealth", "setSystemHealthMax", "setSystemHeat", "setSystemHeatRate", "setSystemPower", "setSystemPowerFactor", "setSystemPowerRate", "setTarget", "setTargetPosition", "setTemplate", "setTexture", "setTubeDirection", "setTubeLoadTime", "setTubes", "setTubeSize", "setType", "setTypeName", "setVariable", "setVelocity", "setWarpDrive", "setWarpSpeed", "setWeaponStorage", "setWeaponStorageMax", "setWeaponTubeCount", "setWeaponTubeDirection", "setWeaponTubeExclusiveFor", "ShipTemplate", "ShipTemplateBasedObject", "showMessage", "shutdownGame", "SpaceObject", "SpaceShip", "SpaceStation", "SupplyDrop", "switchViewToLongRange", "switchViewToMainScreen", "switchViewToScreen", "switchViewToTactical", "takeDamage", "takeReputationPoints", "transferPlayersAtPositionToShip", "transferPlayersToShip", "TutorialGame", "unpauseGame", "victory", "VisualAsteroid", "WarpJammer", "weaponTubeAllowMissle", "weaponTubeDisallowMissle", "WormHole", "Zone",
}

local CLASSES = {
	"Artifact", "Asteroid", "BeamEffect", "BlackHole", "CpuShip", "ElectricExplosionEffect",
	"EMPMissile", "ExplosionEffect", "FactionInfo", "HomingMissile", "HVLI", "Mine",
	"MissileWeapon", "ModelData", "Nebula", "Nuke", "Planet", "PlayerSpaceship", "ScanProbe",
	"ScienceDatabase", "Script", "ScriptStorage", "ShipTemplate", "ShipTemplateBasedObject",
	"SpaceObject", "SpaceShip", "SpaceStation", "SupplyDrop", "TutorialGame", "VisualAsteroid",
	"WarpJammer", "WormHole", "Zone",
}

local explored = {}
function explore(t, path)
	local mt = getmetatable(t)
	if mt ~= "sandbox" then
		if type(t) ~= "table" and mt == nil then
			-- ok; lua code can't setmetatable() a non-table object
		else
			print("Found an unprotected metatable " .. tostring(mt) .. " of " .. tostring(t) .. " (" .. type(t) .. ") at " .. path)
			for k, v in pairs(mt) do
				print("", k, "=>", v)
			end

			for k, v in pairs(mt) do
				explore(k, path .. "(mt)" .. k)
				explore(v, path .. "(mt)" .. k)
			end
		end
	end
	-- setmetatable() won't work if getmetatable() returned "sandbox" or if type~=table

	if type(t) == "table" then
		if explored[t] then return end
		explored[t] = true

		for k, v in pairs(t) do
			explore(k, path .. "." .. tostring(k))
			explore(v, path .. "." .. tostring(k))
		end

		-- Metatable shenanigans means we can't actually iterate all the defined stuff; check for presence of all identifiers that _might_ be defined here
		for _, k in ipairs(IDENTS) do
			local v = t[k]
			if v ~= nil then
				explore(v, path .. ".+" .. k)
			end
		end
	end
end

explore("", "<string>")
explore(0, "<number>")
explore(false, "<bool>")
explore(nil, "<nil>")
explore(_G, "_G")

for _, k in ipairs(CLASSES) do
	explore(_ENV[k](), k .. "()")
end

@GinjaNinja32
Copy link
Contributor Author

output of that stress-test code is currently

INFO: LUA:Found an unprotected metatable table: 0x58be4bed9ba0 of table: 0x58be4c01a770 (table) at _G
INFO: LUA: __index => {}
INFO: LUA:Found an unprotected metatable table: 0x58be4bed9ba0 of table: 0x58be4c01a770 (table) at _G._G
INFO: LUA: __index => {}
INFO: LUA:Found an unprotected metatable table: 0x58be4bed9ba0 of table: 0x58be4c01a770 (table) at _G.+_G
INFO: LUA: __index => {}

i.e. correctly identifying the unprotected metatable on the script environment itself, which is intentional.

src/scriptInterfaceMagic.h Outdated Show resolved Hide resolved
@daid daid merged commit d24b390 into daid:master Aug 2, 2024
9 checks passed
@GinjaNinja32 GinjaNinja32 deleted the sandbox-metatable branch August 3, 2024 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants