Core: support restricting the types an XML preset may load

Prevent potential problems from untrusted XML (typically themes)
traversing into data outside the intended targets.

From what I can tell it's not currently possible but changes to RNA
could allow for this which would likely go by unnoticed.

Further details in code-comments.
This commit is contained in:
Campbell Barton 2024-06-27 21:05:38 +10:00
parent 65d0f365a9
commit 42e1239ba8
3 changed files with 67 additions and 3 deletions

View File

@ -229,14 +229,33 @@ def rna2xml(
fw("{:s}</{:s}>\n".format(root_ident, root_node))
# NOTE(@ideasman42): regarding `secure_types`.
# This is a safe guard when loading an untrusted XML to prevent any possibility of the XML
# paths "escaping" the intended data types, potentially writing into unexpected settings.
# This is done because the XML itself defines the attributes which are recursed into,
# there is a possibility the XML recurse into data that isn't logically owned by "root",
# out of the theme and into user preferences for e.g. which could change trust settings
# even executing code.
#
# At the time of writing it seems this is not possible with themes (the main user of this functionality),
# however this could become possible in the future through additional RNA properties and it wouldn't be
# obvious an exploit existed.
#
# In short, it's safest for users of this API to restrict types when loading untrusted XML.
def xml2rna(
root_xml, *,
root_rna=None, # must be set
secure_types=None, # `Optional[Set[str]]`
):
def xml2rna_node(xml_node, value):
# print("evaluating:", xml_node.nodeName)
if (secure_types is not None) and (xml_node.nodeName not in secure_types):
print("Loading the XML with type restrictions, skipping \"{:s}\"".format(xml_node.nodeName))
return
# ---------------------------------------------------------------------
# Simple attributes
@ -354,7 +373,12 @@ def _get_context_val(context, path):
return value
def xml_file_run(context, filepath, rna_map):
def xml_file_run(
context,
filepath,
rna_map,
secure_types=None, # `Optional[Set[str]]`
):
import xml.dom.minidom
xml_nodes = xml.dom.minidom.parse(filepath)
@ -370,7 +394,7 @@ def xml_file_run(context, filepath, rna_map):
if value is not Ellipsis and value is not None:
# print(" loading XML: {!r} -> {!r}".format(filepath, rna_path))
xml2rna(xml_node, root_rna=value)
xml2rna(xml_node, root_rna=value, secure_types=secure_types)
def xml_file_write(context, filepath, rna_map, *, skip_typemap=None):

View File

@ -274,7 +274,10 @@ class ExecutePreset(Operator):
elif ext == ".xml":
import rna_xml
rna_xml.xml_file_run(context, filepath, preset_class.preset_xml_map)
preset_xml_map = preset_class.preset_xml_map
preset_xml_secure_types = getattr(preset_class, "preset_xml_secure_types", None)
rna_xml.xml_file_run(context, filepath, preset_xml_map, secure_types=preset_xml_secure_types)
_call_preset_cb(getattr(preset_class, "post_cb", None), context, filepath)

View File

@ -931,6 +931,43 @@ class USERPREF_MT_interface_theme_presets(Menu):
("preferences.themes[0]", "Theme"),
("preferences.ui_styles[0]", "ThemeStyle"),
)
# Prevent untrusted XML files "escaping" from these types.
preset_xml_secure_types = {
"Theme",
"ThemeAssetShelf",
"ThemeBoneColorSet",
"ThemeClipEditor",
"ThemeCollectionColor",
"ThemeConsole",
"ThemeDopeSheet",
"ThemeFileBrowser",
"ThemeFontStyle",
"ThemeGradientColors",
"ThemeGraphEditor",
"ThemeImageEditor",
"ThemeInfo",
"ThemeNLAEditor",
"ThemeNodeEditor",
"ThemeOutliner",
"ThemePanelColors",
"ThemePreferences",
"ThemeProperties",
"ThemeSequenceEditor",
"ThemeSpaceGeneric",
"ThemeSpaceGradient",
"ThemeSpaceListGeneric",
"ThemeSpreadsheet",
"ThemeStatusBar",
"ThemeStripColor",
"ThemeStyle",
"ThemeTextEditor",
"ThemeTopBar",
"ThemeUserInterface",
"ThemeView3D",
"ThemeWidgetColors",
"ThemeWidgetStateColors",
}
draw = Menu.draw_preset
@staticmethod