From 92200161edad4202e96af5c7adc3fa6d49aa5ade Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:26:37 +0200 Subject: [PATCH] avm, sonos, yamaha: add parameter to lxml etree parser due to CVE-2026-41066 --- avm/__init__.py | 4 +++- avm/tr064/action.py | 9 +++++++-- avm/tr064/client.py | 4 +++- avm/tr064/service.py | 6 +++++- sonos/soco/data_structures_entry.py | 3 ++- sonos/soco/zonegroupstate.py | 3 ++- yamaha/__init__.py | 27 +++++++++++++++++++-------- 7 files changed, 41 insertions(+), 15 deletions(-) diff --git a/avm/__init__.py b/avm/__init__.py index b03ea5490..d397a3e52 100755 --- a/avm/__init__.py +++ b/avm/__init__.py @@ -4571,4 +4571,6 @@ def request_response_to_xml(request): """ Parse request response to element object """ - return ET.fromstring(request.content) + # added parser with resolve_entities option in response to CVE-2026-41066 + xmlparser = ET.XMLParser(resolve_entities='internal') + return ET.fromstring(request.content, parser=xmlparser) diff --git a/avm/tr064/action.py b/avm/tr064/action.py index 07cad0628..ef701017c 100755 --- a/avm/tr064/action.py +++ b/avm/tr064/action.py @@ -37,6 +37,9 @@ def __init__(self, xml, auth, base_url, name, service_type, service_id, control_ self.namespaces = IGD_SERVICE_NAMESPACE if 'igd' in description_file else TR064_SERVICE_NAMESPACE self.control_namespace = IGD_CONTROL_NAMESPACE if 'igd' in description_file else TR064_CONTROL_NAMESPACE + # added parser with resolve_entities option in response to CVE-2026-41066 + self._xmlparser = ET.XMLParser(resolve_entities='internal') + ET.register_namespace('s', 'http://schemas.xmlsoap.org/soap/envelope/') ET.register_namespace('h', 'http://soap-authentication.org/digest/2001/10/') @@ -92,7 +95,8 @@ def __call__(self, **kwargs): verify=self.verify) if request.status_code != 200: try: - xml = ET.parse(BytesIO(request.content)) + # added parser option in response to CVE-2026-41066 + xml = ET.parse(BytesIO(request.content), parser=self._xmlparser) except Exception: return request.status_code try: @@ -104,7 +108,8 @@ def __call__(self, **kwargs): return error_code if error_code is not None else request.status_code # Translate response and prepare dict - xml = ET.parse(BytesIO(request.content)) + # added parser option in response to CVE-2026-41066 + xml = ET.parse(BytesIO(request.content), parser=self._xmlparser) response = AttributeDict() for arg in list(xml.find('.//{{{}}}{}Response'.format(self.service_type, self.name))): name = self.out_arguments[arg.tag] diff --git a/avm/tr064/client.py b/avm/tr064/client.py index 0b5a9baad..3f48e7048 100755 --- a/avm/tr064/client.py +++ b/avm/tr064/client.py @@ -44,7 +44,9 @@ def _fetch_devices(self, description_file='tr64desc.xml'): # request = requests.get(f'{self.base_url}/{description_file}', verify=self.verify) if request.status_code == 200: - xml = ET.parse(BytesIO(request.content)) + # added parser with resolve_entities option in response to CVE-2026-41066 + xmlparser = ET.XMLParser(resolve_entities='internal') + xml = ET.parse(BytesIO(request.content), parser=xmlparser) for device in xml.findall('.//device', namespaces=self.namespaces): name = device.findtext('deviceType', namespaces=self.namespaces).split(':')[-2] diff --git a/avm/tr064/service.py b/avm/tr064/service.py index e84cce2f7..6e50383a9 100755 --- a/avm/tr064/service.py +++ b/avm/tr064/service.py @@ -27,6 +27,9 @@ def __init__(self, auth, base_url, service_type, service_id, scpdurl, control_ur self.description_file = description_file self.namespaces = IGD_SERVICE_NAMESPACE if 'igd' in description_file else TR064_SERVICE_NAMESPACE + # added parser with resolve_entities option in response to CVE-2026-41066 + self._xmlparser = ET.XMLParser(resolve_entities='internal') + def __getattr__(self, name): if name not in self.actions: self._fetch_actions(self.scpdurl) @@ -40,7 +43,8 @@ def _fetch_actions(self, scpdurl): """Fetch action description.""" request = requests.get('{0}{1}'.format(self.base_url, scpdurl), verify=self.verify) if request.status_code == 200: - xml = ET.parse(BytesIO(request.content)) + # added parser option in response to CVE-2026-41066 + xml = ET.parse(BytesIO(request.content), parser=self._xmlparser) for action in xml.findall('./actionList/action', namespaces=self.namespaces): name = action.findtext('name', namespaces=self.namespaces) diff --git a/sonos/soco/data_structures_entry.py b/sonos/soco/data_structures_entry.py index c3545ff94..de387694d 100755 --- a/sonos/soco/data_structures_entry.py +++ b/sonos/soco/data_structures_entry.py @@ -29,7 +29,8 @@ def from_didl_string(string): list: A list of one or more instances of `DidlObject` or a subclass """ items = [] - parser = ET.XMLParser(recover=True, encoding="utf-8") + # added resolve_entities in response to CVE-2026-41066 + parser = ET.XMLParser(recover=True, encoding="utf-8", resolve_entities='internal') root = ET.fromstring(string.encode("utf-8"), parser=parser) for elt in root: if elt.tag.endswith("item") or elt.tag.endswith("container"): diff --git a/sonos/soco/zonegroupstate.py b/sonos/soco/zonegroupstate.py index 09ab01c0b..40d116044 100755 --- a/sonos/soco/zonegroupstate.py +++ b/sonos/soco/zonegroupstate.py @@ -395,6 +395,7 @@ def update_soco_instances(self, tree): def normalize_zgs_xml(xml): """Normalize the ZoneGroupState payload and return an lxml ElementTree instance.""" - parser = LXML.XMLParser(remove_blank_text=True) # pylint:disable=I1101 + # added resolve_entities in response to CVE-2026-41066 + parser = LXML.XMLParser(remove_blank_text=True, resolve_entities='internal') # pylint:disable=I1101 tree = LXML.fromstring(xml, parser) # pylint:disable=I1101 return ZGS_TRANSFORM(tree) diff --git a/yamaha/__init__.py b/yamaha/__init__.py index 02753c6a0..6aaee7e23 100755 --- a/yamaha/__init__.py +++ b/yamaha/__init__.py @@ -75,6 +75,9 @@ def __init__(self, smarthome): self.mcast_buffer = 1024 self.mcast_service = "urn:schemas-yamaha-com:service:X_YamahaRemoteControl:1" + # added parser with resolve_entities option in response to CVE-2026-41066 + self._xmlparser = etree.XMLParser(resolve_entities='internal') + # On initialization error use: if not REQUIRED_PACKAGE_IMPORTED: self._init_complete = False @@ -194,7 +197,8 @@ def _event_notify(self, value, cmd='PUT'): notice.text = 'On' else: notice.text = 'Off' - tree = etree.ElementTree(root) + # added parser option in response to CVE-2026-41066 + tree = etree.ElementTree(root, parser=self._xmlparser) return self._return_document(tree) def _power(self, value, cmd='PUT'): @@ -209,7 +213,8 @@ def _power(self, value, cmd='PUT'): power.text = 'Standby' elif value == 'GetParam': power.text = value - tree = etree.ElementTree(root) + # added parser option in response to CVE-2026-41066 + tree = etree.ElementTree(root, parser=self._xmlparser) return self._return_document(tree) def _input(self, value, cmd='PUT'): @@ -219,7 +224,8 @@ def _input(self, value, cmd='PUT'): input = etree.SubElement(system, 'Input') input_sel = etree.SubElement(input, 'Input_Sel') input_sel.text = value - tree = etree.ElementTree(root) + # added parser option in response to CVE-2026-41066 + tree = etree.ElementTree(root, parser=self._xmlparser) return self._return_document(tree) def _volume(self, value, cmd='PUT'): @@ -237,7 +243,8 @@ def _volume(self, value, cmd='PUT'): exponent.text = '1' unit = etree.SubElement(level, 'Unit') unit.text = 'dB' - tree = etree.ElementTree(root) + # added parser option in response to CVE-2026-41066 + tree = etree.ElementTree(root, parser=self._xmlparser) return self._return_document(tree) def _mute(self, value, cmd='PUT'): @@ -252,7 +259,8 @@ def _mute(self, value, cmd='PUT'): mute.text = 'Off' elif value == 'GetParam': mute.text = value - tree = etree.ElementTree(root) + # added parser option in response to CVE-2026-41066 + tree = etree.ElementTree(root, parser=self._xmlparser) return self._return_document(tree) def _validate_inputs(self): @@ -261,7 +269,8 @@ def _validate_inputs(self): system = etree.SubElement(root, 'System') state = etree.SubElement(system, 'Config') state.text = 'GetParam' - tree = etree.ElementTree(root) + # added parser option in response to CVE-2026-41066 + tree = etree.ElementTree(root, parser=self._xmlparser) return self._return_document(tree) def _get_state(self): @@ -270,7 +279,8 @@ def _get_state(self): system = etree.SubElement(root, 'Main_Zone') state = etree.SubElement(system, 'Basic_Status') state.text = 'GetParam' - tree = etree.ElementTree(root) + # added parser option in response to CVE-2026-41066 + tree = etree.ElementTree(root, parser=self._xmlparser) return self._return_document(tree) def _get_value(self, notify_cmd, yamaha_host): @@ -292,7 +302,8 @@ def _get_value(self, notify_cmd, yamaha_host): def _return_value(self, state, cmd): try: - tree = etree.parse(StringIO(state)) + # added parser option in response to CVE-2026-41066 + tree = etree.parse(StringIO(state), parser=self._xmlparser) except Exception: return "Invalid data received" if cmd == 'input':