The Strange Case of the Malformed Shebang
The Strange Case of the Malformed Shebang

                
    

        3 minutes

            

            

        
            

            

            
                

Here is a common error you might encounter on Linux: you download a Python script from the internet and try to run it. You receive an error like the following because Python supposedly doesn’t exist:

$ ./script.py
-bash: /path/to/script.py: /usr/local/bin/python^M: bad interpreter: No such file or directory


You try to run python ./script.py and it works. You run which python and verify that python is in fact at the path /usr/local/bin/python. Nothing should be wrong. Why can’t you run ./script.py?

If you need a quick-fix then skip to the end of this post. To explore the issue further, read on!

A Brief Investigation

If you open up script.py then the first line looks exactly like it should:

#!/usr/local/bin/python


This is obviously a shebang which lets the Linux kernel1 know which interpreter should be used to execute the python script. The sharp-eyed readers among you might have already spotted the problem with the shebang earlier. In the error message we have a mysterious ^M character. That’s no coincidence.2

^M, better known as \r, also known as ASCII CR, is the unwanted Windows line ending that occasionally slips its way into Linux files and causes mischief. Here is an excerpt from the load_script function in the Linux kernel which reads shebangs:

i_end = strnchr(bprm->buf, sizeof(bprm->buf), '\n');


As you can see, the kernel assumes that the shebang ends with \n. If the shebang ends with \r\n (as lines typically end on Windows) then \r is included in the interpreter’s name just like any other character.

This explains the original error. The kernel is looking for a python executable which is literally named /usr/local/bin/python^M and no such executable exists.

Two Solutions and a Prank

The easy and obvious solution is to run dos2unix on the python script and strip out the insidious \r character.

The hackish solution is to create a symlink literally named /usr/local/bin/python^M which points to /usr/local/bin/python. This will let you run all such python scripts in the future without running dos2unix on them first.

As for the prank: if you want to drive a co-worker crazy then go one step farther and replace other characters in /usr/local/bin/python with their unicode look-alikes. You can use the homoglyph attack generator to easily do so. For example, try copy-pasting the following shebang into a python file:

#!/usr/local/bin/рythοn


Do you see what I’ve done there? This shebang will never work and it is hard to see why. Try looking at the string in an online hex-editor like hexed.it if you’re confused.

Work with me

Does this sort of thing interest you?

I started as a low-level engineer, but today I’m the co-founder and CEO of Robusta.dev and we’re hiring! I still do the occasional deep technical dive, as well as building a world-class team of excellent engineers and a product used by hundreds of companies.

If you join our team, you’ll work closely with me and be a core part of the founding team. Email [email protected] and mention you came from this post. We hire in Israel as well as remote.


            
        

        

        
                
                    linuxpythoncode-sleuth
                

            541 Words

            2020-11-22 15:16 +0000
        

        
            
        
    

            