Search code examples
pythonlinuxdockercontainersuname

Python program running inside docker container relies upon 'uname -r'


I have a Python program that's designed to run only in certain distros of Linux (i.e. CentOS, Ubuntu, etc..). I'd like to get it running inside a CentOS7 container, but its failing because the following is returning '4.9.49-moby':

import platform
platform.release()

The program is expecting to find a linux kernel release, i.e '3.10.0-327.el7.x86_64'.

Suppose I'm unable to modify the source code of the program.

What are things that I can do that will workaround this issue?

I tried writing a wrapper script around 'uname -r' to return what I want. But this isn't helping since apparently Python is sourcing this directly from the kernel.


Solution

  • Python simply calls the uname system call to get that information, which is always going to return information about the currently running kernel. Overriding the return value without modifying the source is going to be tricky.

    You can accomplish this using function interposition, e.g. as described here. That requires either modifying the image to include both the wrapper library and the necessary environment setup, or it requires you to pass a number of additional parameters on the Docker run command line.

    Here's a simple example. I start with a vanilla image and call os.uname() in Python:

    $ docker run -it --rm fedora python3
    Python 3.6.2 (default, Sep  1 2017, 12:03:48) 
    [GCC 7.1.1 20170802 (Red Hat 7.1.1-7)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import os
    >>> os.uname()
    posix.uname_result(sysname='Linux', nodename='fd2d40cb028b', release='4.13.15-100.fc25.x86_64', version='#1 SMP Tue Nov 21 22:45:32 UTC 2017', machine='x86_64')
    >>> 
    

    I would like the release field to show 1.0.0 instead. I start by creating a wrapper for the uname system call:

    #define _GNU_SOURCE
    #include <dlfcn.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/utsname.h>
    
    /* Function pointers to hold the value of the glibc functions */
    static int (*real_uname)(struct utsname *name) = NULL;
    
    /* wrapping write function call */
    int uname(struct utsname *name) {
        int res;
        real_uname = dlsym(RTLD_NEXT, "uname");
        res = real_uname(name);
        if (res == 0) {
            memset(name->release, 0, _UTSNAME_RELEASE_LENGTH);
            strncpy(name->release, "1.0.0", 5);
        }    
    
        return res;
    }
    

    And I compile the shared library:

    $ gcc -fPIC -shared  -o wrap_uname.so wrap_uname.c -ldl
    

    Now I can inject that into the docker image and preload the shared library. The key additions are are the -v to inject the library and -e LD_PRELOAD to cause the linker to preload it:

    $ docker run -it --rm \
      -v $PWD/wrap_uname.so:/lib/wrap_uname.so \
      -e LD_PRELOAD=/lib/wrap_uname.so fedora python3
    

    And as you can see, that gives us the desired result:

    Python 3.6.2 (default, Sep  1 2017, 12:03:48) 
    [GCC 7.1.1 20170802 (Red Hat 7.1.1-7)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import os
    >>> os.uname()
    posix.uname_result(sysname='Linux', nodename='dd88d697fb65', release='1.0.0', version='#1 SMP Tue Nov 21 22:45:32 UTC 2017', machine='x86_64')
    >>>