diff --git a/data/assets/global/localbookmarks.asset b/data/assets/global/localbookmarks.asset index 93888ace92..dadea5c098 100644 --- a/data/assets/global/localbookmarks.asset +++ b/data/assets/global/localbookmarks.asset @@ -15,15 +15,11 @@ end -- Create bookmarks file if it does not exist if not openspace.fileExists(localBookmarks) then - local file = io.open(localBookmarks, "w") - file:write( - "Group (optional),Name (required),Globe (optional),Lat (required if globe)," .. - "Lon (required if globe),Altitude (optional if globe),x (required if not globe)," .. - "y (required if not globe),z (required if not globe),Scale (optional)," .. - "LineWidth (optional)\n" .. - "NASA,Kennedy Space Center,Earth,28.6658276,-80.70282839,,,,,,\n" + openspace.downloadFile( + "http://liu-se.cdn.openspaceproject.com/files/misc/localbookmarks.csv", + openspace.absPath("${USER}/bookmarks/localbookmarks.csv"), + true ) - file:close() end local nodes = bookmarkHelper.loadBookmarks( diff --git a/data/assets/scene/milkyway/constellations/constellation_art.asset b/data/assets/scene/milkyway/constellations/constellation_art.asset index efc0cef81e..9a0ec35623 100644 --- a/data/assets/scene/milkyway/constellations/constellation_art.asset +++ b/data/assets/scene/milkyway/constellations/constellation_art.asset @@ -22,8 +22,6 @@ local data = asset.resource({ --function that reads the file local function createConstellations(baseIdentifier, guiPath, constellationfile) local genConstellations = {} - --skip the first line - local notFirstLine = false -- define parsec to meters local PARSEC_CONSTANT = 3.0856776E16 -- how many parsecs away do you want the images to be? @@ -31,68 +29,74 @@ local function createConstellations(baseIdentifier, guiPath, constellationfile) -- but they can really be anywhere since the billboard size will scale with distance. local distanceMultiplier = 3.2 local baseScale = 1e17 - for line in io.lines(openspace.absPath(constellationfile)) do - if (notFirstLine) then - -- describes the data - local matchstring = "(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-)$" - local group, abbreviation, name, x, y, z, scale, imageName, rotX, rotY, rotZ, centerStar = line:match(matchstring) - local magVec = math.sqrt(x*x + y*y + z*z) - local normx = x / magVec - local normy = y / magVec - local normz = z / magVec + local lines = openspace.readCSVFile(openspace.absPath(constellationfile)) + for _, line in ipairs(lines) do + -- describes the data + local group = line[1] + local abbreviation = line[2] + local name = line[3] + local x = tonumber(line[4]) + local y = tonumber(line[5]) + local z = tonumber(line[6]) + local scale = tonumber(line[7]) + local imageName = line[8] + local rotX = tonumber(line[9]) + local rotY = tonumber(line[10]) + local rotZ = tonumber(line[11]) + local centerStar = line[12] + local magVec = math.sqrt(x*x + y*y + z*z) + local normx = x / magVec + local normy = y / magVec + local normz = z / magVec - -- Use the full name in the data constellations.dat if possible - -- Otherwise, use the given name in the constellation_data.csv file - local foundName = constellations_helper.findFullName(abbreviation) - if foundName ~= nil then - name = foundName - end + -- Use the full name in the data constellations.dat if possible + -- Otherwise, use the given name in the constellation_data.csv file + local foundName = constellations_helper.findFullName(abbreviation) + if foundName ~= nil then + name = foundName + end - group = (group == "" and globe or group) + group = (group == "" and globe or group) - local aconstellation = { - -- Identifier = guiPath .. "-" .. name, - Identifier = baseIdentifier .. "-" .. abbreviation, - Parent = transforms.SolarSystemBarycenter.Identifier, - Transform = { - Translation = { - Type = "StaticTranslation", - -- position is in parsecs from the SolarSystemBarycenter, so convert to meters - Position = { - normx * PARSEC_CONSTANT * distanceMultiplier, - normy * PARSEC_CONSTANT * distanceMultiplier, - normz * PARSEC_CONSTANT * distanceMultiplier - } - }, - Rotation = { - Type = "StaticRotation", - Rotation = { tonumber(rotX), tonumber(rotY), tonumber(rotZ) } + local aconstellation = { + -- Identifier = guiPath .. "-" .. name, + Identifier = baseIdentifier .. "-" .. abbreviation, + Parent = transforms.SolarSystemBarycenter.Identifier, + Transform = { + Translation = { + Type = "StaticTranslation", + -- position is in parsecs from the SolarSystemBarycenter, so convert to meters + Position = { + normx * PARSEC_CONSTANT * distanceMultiplier, + normy * PARSEC_CONSTANT * distanceMultiplier, + normz * PARSEC_CONSTANT * distanceMultiplier } }, - Renderable = { - Type = "RenderablePlaneImageLocal", - Enabled = false, - Size = tonumber(baseScale * scale * distanceMultiplier / 10), - Origin = "Center", - Billboard = false, - LazyLoading = true, - Texture = images .. imageName, - BlendMode = "Additive", - Opacity = 0.1, - DimInAtmosphere = true - }, - Tag = { "ImageConstellation", group, "daytime_hidden" }, - GUI = { - Name = name .. " Image", - Path = "/Milky Way/Constellations/" .. guiPath, - Description = name .. " Image" + Rotation = { + Type = "StaticRotation", + Rotation = { tonumber(rotX), tonumber(rotY), tonumber(rotZ) } } + }, + Renderable = { + Type = "RenderablePlaneImageLocal", + Enabled = false, + Size = tonumber(baseScale * scale * distanceMultiplier / 10), + Origin = "Center", + Billboard = false, + LazyLoading = true, + Texture = images .. imageName, + BlendMode = "Additive", + Opacity = 0.1, + DimInAtmosphere = true + }, + Tag = { "ImageConstellation", group, "daytime_hidden" }, + GUI = { + Name = name .. " Image", + Path = "/Milky Way/Constellations/" .. guiPath, + Description = name .. " Image" } - table.insert(genConstellations, aconstellation) - - else - notFirstLine = true - end + } + table.insert(genConstellations, aconstellation) end return genConstellations end diff --git a/data/assets/util/constellations_helper.asset b/data/assets/util/constellations_helper.asset index ffbce5157c..69975b7a99 100644 --- a/data/assets/util/constellations_helper.asset +++ b/data/assets/util/constellations_helper.asset @@ -11,14 +11,10 @@ local data = asset.resource({ -- If the file does not exist or if a match could not be found, it returns nil local function findFullName(abbreviation) local namesFile = data .. "constellations.dat" - local file = io.open(namesFile, "r") local fullName = "" - if file == nil then - return nil - end - - for line in io.lines(namesFile) do + local lines = openspace.readFileLines(namesFile) + for _, line in ipairs(lines) do -- Try and find the identifier in the file local index, length = line:find(abbreviation) if index ~= nil and index < 4 then @@ -37,7 +33,6 @@ local function findFullName(abbreviation) end end - file:close() if fullName == "" then openspace.printError( "Error when calling function 'findFullName' in file 'util/constellations_helper': " .. diff --git a/ext/ghoul b/ext/ghoul index 7eb3d942c4..b0517f0be9 160000 --- a/ext/ghoul +++ b/ext/ghoul @@ -1 +1 @@ -Subproject commit 7eb3d942c48f8678519ffcd57c325db35bd192d8 +Subproject commit b0517f0be9d4087328c6ebb5811141c801864089 diff --git a/include/openspace/engine/configuration.h b/include/openspace/engine/configuration.h index 7bb5abeacc..2c6a624001 100644 --- a/include/openspace/engine/configuration.h +++ b/include/openspace/engine/configuration.h @@ -105,6 +105,8 @@ struct Configuration { bool shouldUseScreenshotDate = false; + bool sandboxedLua = true; + std::string onScreenTextScaling = "window"; bool usePerProfileCache = false; diff --git a/include/openspace/scripting/scriptengine.h b/include/openspace/scripting/scriptengine.h index 8776856ec4..ff6c37e976 100644 --- a/include/openspace/scripting/scriptengine.h +++ b/include/openspace/scripting/scriptengine.h @@ -62,7 +62,7 @@ public: static constexpr std::string_view OpenSpaceLibraryName = "openspace"; - ScriptEngine(); + explicit ScriptEngine(bool sandboxedLua = true); /** * Initializes the internal Lua state and registers a common set of library functions. diff --git a/openspace.cfg b/openspace.cfg index 042913cfe5..4de0cbfe0b 100644 --- a/openspace.cfg +++ b/openspace.cfg @@ -226,6 +226,8 @@ LogEachOpenGLCall = false PrintEvents = false ConsoleKey = "GRAVEACCENT" +SandboxedLua = true + ShutdownCountdown = 3 ScreenshotUseDate = true BypassLauncher = false diff --git a/src/engine/configuration.cpp b/src/engine/configuration.cpp index cd2d26d9e4..ff22ad6ae7 100644 --- a/src/engine/configuration.cpp +++ b/src/engine/configuration.cpp @@ -191,6 +191,12 @@ namespace { // pass beyond local midnight std::optional screenshotUseDate; + // Toggles whether the Lua states used inside OpenSpace are sandboxed which + // prevents potentially unsafe malicious code to run on the system. Only turn this + // setting to `false` if you are sure that no external code from asset files, + // session recordings, or parallel connections can be executed on this machine. + std::optional sandboxedLua; + struct HttpProxy { // Determines whether the proxy is being used std::optional activate; @@ -405,6 +411,7 @@ ghoul::Dictionary Configuration::createDictionary() { res.setValue("ConsoleKey", ghoul::to_string(consoleKey)); res.setValue("ShutdownCountdown", static_cast(shutdownCountdown)); res.setValue("shouldUseScreenshotDate", shouldUseScreenshotDate); + res.setValue("sandboxedLua", sandboxedLua); res.setValue("OnScreenTextScaling", onScreenTextScaling); res.setValue("UsePerProfileCache", usePerProfileCache); res.setValue("IsRenderingOnMasterDisabled", isRenderingOnMasterDisabled); @@ -562,6 +569,7 @@ void parseLuaState(Configuration& configuration) { c.shutdownCountdown = p.shutdownCountdown.value_or(c.shutdownCountdown); c.shouldUseScreenshotDate = p.screenshotUseDate.value_or(c.shouldUseScreenshotDate); + c.sandboxedLua = p.sandboxedLua.value_or(c.sandboxedLua); c.onScreenTextScaling = p.onScreenTextScaling.value_or(c.onScreenTextScaling); c.usePerProfileCache = p.perProfileCache.value_or(c.usePerProfileCache); c.isRenderingOnMasterDisabled = @@ -705,6 +713,9 @@ Configuration loadConfigurationFromFile(const std::filesystem::path& configurati ghoul_assert(std::filesystem::is_regular_file(configurationFile), "File must exist"); Configuration result; + // Having the configuration not sandboxed is safe as there is no way for a third-party + // file to have any input this early in the loading phase + result.state = ghoul::lua::LuaState(ghoul::lua::LuaState::Sandboxed::No); // Injecting the resolution of the primary screen into the Lua state const std::string script = std::format( diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index ff8bbbb243..3f04d62077 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -389,6 +389,17 @@ void OpenSpaceEngine::initialize() { // Register the provided shader directories ghoul::opengl::ShaderPreprocessor::addIncludePath(absPath("${SHADERS}")); + if (!global::configuration->sandboxedLua) { + // The Lua state is sandboxed by default, so if the user wants an unsandboxed one, + // we have to recreate it here. + // @TODO (2024-08-07, abock) It's not pretty, but doing it differently would + // require a bigger rewrite of how we handle the ScriptEngine + global::scriptEngine->~ScriptEngine(); + global::scriptEngine = new (global::scriptEngine) scripting::ScriptEngine( + global::configuration->sandboxedLua + ); + } + // Register Lua script functions LDEBUG("Registering Lua libraries"); registerCoreClasses(*global::scriptEngine); @@ -906,7 +917,9 @@ void OpenSpaceEngine::runGlobalCustomizationScripts() { ZoneScoped; LINFO("Running Global initialization scripts"); - const ghoul::lua::LuaState state; + const ghoul::lua::LuaState state = ghoul::lua::LuaState( + ghoul::lua::LuaState::Sandboxed::No + ); global::scriptEngine->initializeLuaState(state); for (const std::string& script : global::configuration->globalCustomizationScripts) { diff --git a/src/scripting/scriptengine.cpp b/src/scripting/scriptengine.cpp index faa268c8dc..636ce6f750 100644 --- a/src/scripting/scriptengine.cpp +++ b/src/scripting/scriptengine.cpp @@ -80,15 +80,14 @@ namespace { return result; } - - - #include "scriptengine_codegen.cpp" } // namespace namespace openspace::scripting { -ScriptEngine::ScriptEngine() {} +ScriptEngine::ScriptEngine(bool sandboxedLua) + : _state(ghoul::lua::LuaState::Sandboxed(sandboxedLua)) +{} void ScriptEngine::initialize() { ZoneScoped;