Skip to content

TypeError: unsupported operand type(s) for +: 'NoneType' and 'list' in subprocess.Popen when using shell=True under strict Java SecurityManager #418

@akspi

Description

@akspi

Description

When running Jython within a sandboxed JVM environment that utilizes a strict java.lang.SecurityManager (specifically, one that denies java.io.FilePermission for reading system directories like /bin/),
executing commands via the subprocess module with shell=True (or using wrappers like os.popen or os.system) crashes with a cryptic Python TypeError rather than throwing a descriptive OSError or
Java SecurityException.

Environment

  • Jython Version: 2.5.x, 2.7.x (including 2.7.3 and current development branch)
  • OS: Linux/Unix (where _shell_command resolution relies on /bin/sh)
  • JVM State: Running with a SecurityManager that denies read access to standard system executable paths.

Steps to Reproduce

Run a Jython script that attempts to use os.popen() or subprocess.Popen(..., shell=True) inside a JVM where the SecurityManager denies file read access to /bin/sh.

Minimal Jython Script:

  import java.lang.SecurityManager as SecurityManager
  import java.lang.System as System
  import java.security.AccessControlException as AccessControlException

  class MySecurityManager(SecurityManager):
      def checkRead(self, file, context=None):
          if file == "/bin/sh" or file == "cmd.exe" or file.endswith("sh"):
              raise AccessControlException("Access denied: " + file)
      def checkPermission(self, perm, context=None):
          pass

  System.setSecurityManager(MySecurityManager())
import subprocess
# This will crash with a TypeError
subprocess.Popen(["whoami"], shell=True)
**Stack Trace:**
  test_sm.py:16: RuntimeWarning: Unable to determine _shell_command for underlying os: posix
    import subprocess
  Traceback (most recent call last):
    File "test_sm.py", line 17, in <module>
      subprocess.Popen(["whoami"], shell=True)
    File ".../Lib/subprocess$py.class", line 892, in __init__
    File ".../Lib/subprocess$py.class", line 1352, in _execute_child
  TypeError: unsupported operand type(s) for +: 'NoneType' and 'list'

Root Cause Analysis

The bug resides in Lib/subprocess.py and stems from how Jython caches the default system shell executable during module initialization.

  1. Initialization (_setup_platform): When the subprocess module is loaded, it attempts to determine the system shell by iterating through known shell paths (e.g., /bin/sh). It validates these paths
    using os.path.exists(executable).
  2. SecurityManager Intervention: Because the JVM is sandboxed, os.path.exists("/bin/sh") triggers the SecurityManager, which throws an AccessControlException.
  3. Silent Failure: Jython's os.path.exists() implementation catches this Java security exception and simply returns False.
  4. Invalid State: Because os.path.exists() returns False for all candidate shells, the global variable _shell_command is never assigned a valid list (like ["/bin/sh", "-c"]) and remains
    initialized to None.
  5. The Crash (_execute_child and system): Later, when the user invokes subprocess.Popen(..., shell=True) or os.system(), the code attempts to prepend the shell command to the user's arguments:
   if shell:
       args = _shell_command + args # <--- CRASH HERE
   Because `_shell_command` is `None` and `args` is a list, Python throws the `TypeError`.

Expected Behavior

Instead of a cryptic TypeError about concatenating NoneType and list, Jython should gracefully handle the scenario where a shell cannot be located (whether due to actual absence or SecurityManager
restrictions).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions