GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — mishandling of c-strings in parser · Issue #96670 · python/cpython

mishandling of c-strings in parser #96670

New issue
Copy link
New issue
Copy link
Closed
Closed
mishandling of c-strings in parser#96670
Copy link
Labels
type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@asottile
asottile
opened on Sep 8, 2022
Issue body actions

Bug report

the parser mishandles lines containing null bytes when parsing source -- this allows the code to be misleadingly different from what it looks like.

I've been told by security@ that it is ok to post this publicly.

in the below example, <NUL> is an actual null byte:

x = '<NUL>' nothing to see here
';import os;os.system('echo pwnd')

and the execution and appearance in the terminal:

$ cat t.py
x = '' nothing to see here
';import os;os.system('echo pwnd')
$ python3 t.py
pwnd

it appears that after splitting the source into lines, the individual lines are treated as c strings and so the null terminator is misinterpreted, jamming the string contents together and it executes similar to this:

x = '';import os;os.system('echo pwnd')

note that if you want to write out a file like this here's a simple bit of code you can paste into an interactive prompt:

open('t.py', 'w').write("x = '\0' nothing to see here\n';import os;os.system('echo pwnd')\n")

here is perhaps a shorter example:

open('t.py', 'w').write("x = 1\0 + 1\n+2\nprint(x)\n")

I originally found this due to a bug report where the ast parser rejects code containing null bytes:

>>> import ast
>>> ast.parse("x = '\0'")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/ast.py", line 47, in parse
    return compile(source, filename, mode, flags,
ValueError: source code string cannot contain null bytes
>>> ast.parse(b"x = '\0'")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/ast.py", line 47, in parse
    return compile(source, filename, mode, flags,
ValueError: source code string cannot contain null bytes

ideally I would want the interpreter to reject files containing null bytes as a SyntaxError (and update the ast.parse error to a SyntaxError as well) -- though it appears there are some of these files in the wild -- such as https://github.com/univention/univention-corporate-server/blob/5.0-2/services/univention-ldb-modules/buildtools/bin/waf-svn

Your environment

  • CPython versions tested on: 3.7 ... 3.11rc1 (though pretty sure this reproduces on all versions)
  • Operating system and architecture: ubuntu 22.04, linux, x86_64

Linked PRs

  • [3.11] gh-96670: Raise SyntaxError when parsing NULL bytes (GH-97594) #104195
Reacted by qexat, Adam Johnson, Stepan, crazyman and yj94
Reacted by Eddie Darling
Reacted by Shaw, Ferran Jovell, Jasper Spaans, Dionisio E Alonso, Henry Schreiner and Zeyu (Zayne) Zhang
Reacted by Marsha, Bernát Gábor, Amin Alaee, Dionisio E Alonso, Yaroslav Pankovych, Shakya Majumdar and Ruairidh MacLeod

Activity

Next
  1. asottile
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Sep 8, 2022
  2. encukou commented on Sep 8, 2022

    @encukou
    encukou
    on Sep 8, 2022
    Member
    More actions

    There was a discussion about the larger issue of code being ”misleadingly different from what it looks like", and the general consensus was that this should be solved in editors, linters and code review tools, rather than in Python. IMO it was this thread but might need more digging.
    That doesn't mean handling of \0 can't be changed. Or of control characters in general: see the informational PEP 672 for some more details.
    Perhaps all unusual control characters should be banned? Even in strings? With a warning for 2 releases, according to the backwards compatibility policy – waf would need to switch to e.g. base64.

    Reacted by Gregory P. Smith
  3. asottile commented on Sep 8, 2022

    @asottile
    asottile
    on Sep 8, 2022
    ContributorAuthor
    More actions

    that was the rationale for making this public, but it's different than those as it's a mishandling by the parser rather than a quirk of how unicode displays

    Reacted by Henry Schreiner, suic86, NogNoa, saurabh chopra and Tushar Sadhwani
  4. ypankovych commented on Sep 9, 2022

    @ypankovych
    ypankovych
    on Sep 9, 2022
    Contributor
    More actions

    Ugh, this is freakin amazing.

    C:\Users\user>more t.py
    
    """
    How do i print 'Hello World' in Python?
    
    Here is how:
    print('Hello World')
    """
    

    And run it:

    C:\Users\user>py t.py
    Hello World
    

    open('t.py', 'w').write("\0'''\0How do i print \'Hello World\' in Python?\n\0Here is how:\nprint(\'Hello World\')\0'''")

    Reacted by Eddie Darling
  5. gpshead commented on Sep 13, 2022

    @gpshead
    gpshead
    on Sep 13, 2022
    Member
    More actions

    ideally I would want the interpreter to reject files containing null bytes as a SyntaxError (and update the ast.parse error to a SyntaxError as well)

    FWIW I'd be in favor of that behavior change as a Feature. I wouldn't backport it as a bug though.

    Reacted by Guido van Rossum, Tim Hatch, Zachary Ware and Tushar Sadhwani
  6. asottile commented on Sep 13, 2022

    @asottile
    asottile
    on Sep 13, 2022
    ContributorAuthor
    More actions

    there's kinda 3 things I think:

    1. convert ValueError => SyntaxError in ast.parse (probably 3.12+)
    2. treat source files containing null bytes as SyntaxError (probably also 3.12+)
    3. fix the thing considering it as a c string by using (char*, size_t) or whatever function (probably backportable?)

    does it make sense to pursue these three things and separately?

  7. gvanrossum commented on Sep 13, 2022

    @gvanrossum
    gvanrossum
    on Sep 13, 2022
    Member
    More actions

    there's kinda 3 things I think:

    1. convert `ValueError` => `SyntaxError` in `ast.parse` (probably 3.12+)
    

    Agreed, it's changing behavior, so can't backport. I like SyntaxError for this situation. The root exception isn't in ast.parse(), it's in compile(), probably even deeper.

    2. treat source files containing null bytes as `SyntaxError` (probably also 3.12+)
    

    That's the biggest hole, should probably do this first.

    3. fix the thing considering it as a c string by using `(char*, size_t)` or whatever function (probably backportable?)
    

    Depends on where that fix is (can you tell I haven't looked at the source code yet? :-). If any of the affected functions are public it's going to be more difficult.

    does it make sense to pursue these three things and separately?

    Likely.

    Reacted by Nick and saurabh chopra
    Reacted by Nick
  8. gpshead commented on Sep 14, 2022

    @gpshead
    gpshead
    on Sep 14, 2022
    Member
    More actions
    2. treat source files containing null bytes as `SyntaxError` (probably also 3.12+)
    

    That's the biggest hole, should probably do this first.

    Be careful about this one. Python pre-pended to raw binary data for the executed Python to locate within the file and use (embedded zip or other data) is a common idiom that must continue to work.

  9. gvanrossum commented on Sep 14, 2022

    @gvanrossum
    gvanrossum
    on Sep 14, 2022
    Member
    More actions

    Be careful about this one. Python pre-pended to raw binary data for the executed Python to locate within the file and use (embedded zip or other data) is a common idiom that must continue to work.

    Are you sure that works? Unlike Unix shells, Python parses the entire source file before executing any code. How would you get Python to ignore a blob of arbitrary binary data embedded in the source code, even if \0 is accepted? If the blob contains \n characters you can't hide it behind a # comment. I suppose you could prefix it with a """ quote, if you can arrange for the file to also end in """, and you're lucky that the blob doesn't contain embedded """ sequences.

    But if I had to do something like that I'd probably just embed the Python code in a bash script as a "here" document and end the bash script with exit.

  10. gpshead commented on Sep 14, 2022

    @gpshead
    gpshead
    on Sep 14, 2022
    Member
    More actions

    Oh you're right, I guess what I've seen do that is a bash+python+data hybrid monster.

  11. gvanrossum commented on Sep 14, 2022

    @gvanrossum
    gvanrossum
    on Sep 14, 2022
    Member
    More actions

    Okay then we should be safe banning \0 in files starting with 3.12.

    Reacted by qexat and Tushar Sadhwani
  12. asottile commented on Sep 14, 2022

    @asottile
    asottile
    on Sep 14, 2022
    ContributorAuthor
    More actions

    I suspect one could probably hack something together like this unfortunately

    # coding: latin1
    with open(__file__, 'rb') as f:
        contents = f.read().split(b'### BINARY\n')[1]
    GARB = '''\
    ### BINARY
    (actual binary here)
    ### BINARY
    '''
  13. gvanrossum commented on Sep 14, 2022

    @gvanrossum
    gvanrossum
    on Sep 14, 2022
    Member
    More actions

    You'd still have to arrange for the actual binary not to contain the sequence ''' -- because that would end the string started at GARB =. I don't see how setting the coding to Latin-1 changes matters. (Honestly it seems you're making the same mistake as Greg.)

  14. asottile commented on Sep 14, 2022

    @asottile
    asottile
    on Sep 14, 2022
    ContributorAuthor
    More actions

    latin1 is to prevent a decoding error while parsing the source -- I linked an in-the-wild example of such a file in the original post

  15. gvanrossum commented on Sep 14, 2022

    @gvanrossum
    gvanrossum
    on Sep 14, 2022
    Member
    More actions

    Okay, that's impressive. It looks like the blob is lightly encoded -- \n and \r are encoded using #. and #&. (I'm curious what they'd do if the blob contains one of those sequences, there doesn't seem to be a way to quote them.)

    The proposed change in 3.12 will break them, but they have version checks and they can just cope with it, I don't think we need to preserve this machine-dependent quirk forever. The code looks like it had to deal with various other versioning issues already (e.g. it tries to handle Python 2 and 3!).

    Reacted by Gregory P. Smith and Tushar Sadhwani
  16. 12 remaining items

  17. pablogsal
    added 7 commits that reference this issue on Sep 27, 2022

    pythongh-96670: Raise SyntaxError when parsing NULL bytes

    750c691

    pythongh-96670: Raise SyntaxError when parsing NULL bytes

    97a8f83

    pythongh-96670: Raise SyntaxError when parsing NULL bytes

    cb89392

    pythongh-96670: Raise SyntaxError when parsing NULL bytes

    490f5bd

    pythongh-96670: Raise SyntaxError when parsing NULL bytes

    4b1105c

    pythongh-96670: Raise SyntaxError when parsing NULL bytes

    e10909d

    gh-96670: Raise SyntaxError when parsing NULL bytes (#97594)

    aab01e3
  18. gvanrossum commented on Sep 27, 2022

    @gvanrossum
    gvanrossum
    on Sep 27, 2022
    Member
    More actions

    Closing this. Starting with Python 3.12, NUL bytes won't be allowed in source code read from files. We're not backporting this since this could be considered a feature by some.

  19. gvanrossum
    closed this as completedon Sep 27, 2022
  20. vstinner commented on Sep 28, 2022

    @vstinner
    vstinner
    on Sep 28, 2022
    Member
    More actions

    Starting with Python 3.12, NUL bytes won't be allowed in source code read from files

    It was changed by the commit aab01e3: PR #97594.

  21. CarliJoy commented on Oct 17, 2022

    @CarliJoy
    CarliJoy
    on Oct 17, 2022
    More actions

    Just for reference: The problem is already mentioned in https://peps.python.org/pep-0672/#control-characters

    And Pylint checks for null characters already.

    Reacted by Victor Stinner
  22. lysnikolaou
    added a commit that references this issue on May 5, 2023

    [3.11] pythongh-96670: Raise SyntaxError when parsing NULL bytes (pyt…

    e6d28ba
  23. pablogsal
    added a commit that references this issue on May 7, 2023

    [3.11] gh-96670: Raise SyntaxError when parsing NULL bytes (GH-97594) (…

    a09d390
  24. ambv
    added 2 commits that reference this issue on Oct 21, 2026

    Fix CI: move news entry out of spaced directory (pythongh-96670)

    46d7860

    Fix CI: move news entry out of spaced directory (pythongh-96670)

    7f37169
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.