44#
55# SPDX-License-Identifier: LGPL-3.0-or-later
66#
7- # Last Updated: 4-03 -2026
7+ # Last Updated: 4-04 -2026
88
99"""Rohde and Schwarz Converter"""
1010
1111import io
12+ import os
1213import logging
1314import tarfile
1415import getpass
1516import tempfile
16- import xml .etree .ElementTree as ET
17+ from defusedxml .ElementTree import parse
18+
19+
1720from datetime import datetime , timedelta , timezone
1821from pathlib import Path
1922from typing import List , Optional , Tuple
@@ -60,6 +63,26 @@ def xml_to_dict(elem):
6063 return result
6164
6265
66+ def is_safe_member (tar , member , target_dir ):
67+ """
68+ Ensure the member will extract inside target_dir.
69+ Prevents path traversal attacks.
70+ """
71+ member_path = os .path .join (target_dir , member .name )
72+ abs_target = os .path .abspath (target_dir )
73+ abs_member = os .path .abspath (member_path )
74+
75+ return abs_member .startswith (abs_target )
76+
77+ def safe_extract (tar , target_dir ):
78+ """
79+ Extract only safe members from a tarfile.
80+ """
81+ for member in tar .getmembers ():
82+ if not is_safe_member (tar , member , target_dir ):
83+ raise Exception (f"Unsafe path detected in TAR: { member .name } " )
84+ tar .extract (member , target_dir )
85+
6386def extract_iq_tar_to_directory (rohdeschwarz_path , file_dest_dir = None ):
6487 tar_path = Path (rohdeschwarz_path )
6588
@@ -69,7 +92,7 @@ def extract_iq_tar_to_directory(rohdeschwarz_path, file_dest_dir=None):
6992 file_dest_dir .mkdir (parents = True , exist_ok = True )
7093
7194 with tarfile .open (tar_path , "r" ) as tar :
72- tar . extractall ( file_dest_dir )
95+ safe_extract ( tar , file_dest_dir )
7396
7497 xml_files = list (file_dest_dir .glob ("*.xml" ))
7598 if not xml_files :
@@ -78,7 +101,7 @@ def extract_iq_tar_to_directory(rohdeschwarz_path, file_dest_dir=None):
78101 # Assuming there is only one XML file in the archive, return its path for further processing
79102 return xml_files [0 ]
80103
81- def _text_of (root : ET . Element , tag : str ) -> Optional [str ]:
104+ def _text_of (root , tag : str ) -> Optional [str ]:
82105 """Extract and strip text from XML element."""
83106 elem = root .find (tag )
84107 return elem .text .strip () if (elem is not None and elem .text is not None ) else None
@@ -97,7 +120,7 @@ def validate_rohdeschwarz(xml_path: Path) -> None:
97120 SigMFConversionError
98121 If required fields are missing or invalid, or IQ file doesn't exist.
99122 """
100- tree = ET . parse (xml_path )
123+ tree = parse (xml_path )
101124 root = tree .getroot ()
102125
103126 # validate CenterFrequency
@@ -186,7 +209,7 @@ def _build_metadata(xml_path: Path) -> Tuple[dict, dict, list, int]:
186209 log .info ("converting rohdeschwarz xml metadata to sigmf format" )
187210
188211 xml_path = Path (xml_path )
189- tree = ET . parse (xml_path )
212+ tree = parse (xml_path )
190213 root = tree .getroot ()
191214
192215 # validate required fields and associated IQ file
0 commit comments