Thread: File Descriptor Redirection Issue

  1. #1
    Registered User
    Join Date
    Jan 2015
    Posts
    2

    File Descriptor Redirection Issue

    Hello,

    I am relatively new to C programming, and have encountered a strange issue.

    Background:
    While playing around with linux, I got an idea to make a C program to encapsulate a shell script to provide a more secure approach to executing scripts through SUID. While this approach is not exactly completely secure, it was more of a short (or so I thought) learning experience. I created a small program which would have a shell script appended to it. Upon execution it would access the script through a file descriptor and redirect it to stdin. From there it would use an exec call to run the shell and thus allow a script to execute with Set User ID.

    Problem:
    Everything was working fine until I realized that under a very specific set of circumstances the file descriptor redirection would fail (although it never threw an error) and the contents of the file would not get to stdin. After trying to debug this issue for a good couple of hours I thought I would reach out to the community and see if anyone had any insight on this issue (or if maybe I missed something through lack of experience)

    I am running Linux Mint 17. Out-of-the-box bash shell.

    The simple explanation of what I did is:
    main.c is compiled. bash2bin.c is compiled and used to append a small shell script onto main. main is executed and it reads the shell script from its own file. It reads the shebang when it is present (that's when the program fails to redirect stdin) and tries to launch the script through the correct shell interpreter. When no shebang is present, it defaults to /bin/bash and everything works fine.

    So to phrase it as a question; Why does the code run just fine when the first line of test.txt is removed? Is it a bug that I haven't found (after hours of looking)? Some obscure bug somewhere deep inside linux (highly unlikely, I know)? Is something corrupt in my installation?

    I know it's a fair amount of code, but it's well commented and any help would be appreciated.

    The procedure I used for compiling and running this somewhat convoluted project:
    Code:
    #!/bin/bash
    
    gcc -Wextra -ggdb main.c -o main
    gcc -Wextra -ggdb bash2bin.c -o bash2bin
    
    
    ./bash2bin < test.txt
    
    
    ./main
    
    
    typeset -i error=$?
    
    
    if ((${error}!=0)); then
        echo "Error: ${error}";
    fi
    bash2bin.c:
    Used for attaching the script to main
    Code:
    #include <stdio.h>#include <stdlib.h>
    #include <unistd.h>
    
    
    char magic[] = {0x23, 0x22, 0xF0, 0x48, 0x00, 0x52, 0xA1, 0x44}; //The same magic constant as in the other file
    
    
    /*
    * Simple helper error method. Used to terminate the program with error code "1" and print a String to stderr.
    */
    void error(char* errMsg) {
        fprintf(stderr, "%s\n", errMsg);
        exit(1);
    }
    
    
    int main() {
        
        FILE *my_file = fopen("main", "a"); //Append to the main binary (compiled from main.c)
        
        if(!my_file) //Error check
            error("Failed to open the file for reading!"); 
            
        int fd = fileno(my_file); //Get the fd.
        
        lseek(fd, 0, SEEK_END); //Seek to end (I didn't get along with append for some reason)
        
        write(fd, magic, 8); //Write the magic constant to the end of the file (or the begginging of the shell script)
        
        char buffer[1024];
        
        int total_read = 0;
        
        /*
        * Copy the data from the shell script (read from stdin, in reality it is test.txt) to the end of the main binary
        */
        while(1) {
            int dataRead = read(STDIN_FILENO, buffer, 1024);
            total_read += dataRead;
            if(dataRead < 1)
                break;
            write(fd, buffer, dataRead);
        }
    
    
        fclose(my_file); //Close the main binary
    
    
        return 0;
    }
    main.c
    The main code, which has the bash script appended to it. This is where the problem is:
    Code:
    #include <stdio.h>#include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <errno.h>
    
    
    char magic[] = {0x23, 0x22, 0xF0, 0x48, 0x00, 0x52, 0xA1, 0x44}; //The constant the program looks for in the file to begin reading
    
    
    /*
    * Stadard error method. Exits the program with "1" error code.
    * errMsg - String which will be printed to stderr before terminating
    */
    void error(char* errMsg) {
        fprintf(stderr, "%s\n", errMsg);
        exit(1);
    }
    
    
    /*
    * Checks the rest of the magic constant, once the first character has been found.
    */
    int checkRestOfSequance(int fd) {
        int currentMagic = 1;
        char buffer[1];
        while(1) {
            if(!read(fd,&buffer,1))
                error("Reached EOF without finding magic number."); 
            if(buffer[0] != magic[currentMagic++])
                return 0;
            if(currentMagic==8)
                break;
        }
        return 1;
    }
    
    
    /*
    * Goes through the file until the magic constant is located.
    * In reallity it is looking for the second magic constant as though the first will be the definition of the above field.
    */
    void seekFdToValue(int fd) {
        
        char buffer[1];
        
        int magicCount=0;
        
        while(1) {
            if(!read(fd,&buffer,1))
                error("Reached EOF without finding magic number.");
            if(buffer[0]==magic[0]) {
                if(checkRestOfSequance(fd)) {
                    if(++magicCount==2)
                        break;
                }
            }
                 
        }
        
    }
    
    
    void tryParseShebang(FILE* file, char* shell, unsigned int shell_len) {
        char shebang[3] = {0}; //Three bytes allocated to store possible shebang. e.g. #!\0
        char ret_shell[100] = {0}; //This will store the shell behind the shebang or the default /bin/bash
        fscanf(file, "%2s", shebang); //read two characers, which may or may not be #!
        
        /*
        * When the first result of the "if" executes, the script works fine,
        * When the else executes, redirection from file to stdin fails (although no error code is ever returned).
        */
        if(strcmp(shebang, "#!") != 0) { //If the script has no shebang. 
            fseek(file, -2, SEEK_CUR); //Go back 2 bytes, so as to leave the two characters in the "stream"
            strcpy(ret_shell, "/bin/bash"); //Default to /bin/bash
        } else {
            //fseek(file, -2, SEEK_CUR); //Different attempts to get this working
            
            //char c;
            //int idx = 0;
            //while((c=fgetc(file))!='\n')
            //    ret_shell[idx++]=c;
            
            //fgets(ret_shell, 100, file); 
            
            fscanf(file, "%100[^\n]\n", ret_shell); //This line, as well as the ones above it, cause the problem.
        }
        if(strlen(ret_shell) > shell_len) //If there is not enough space to safely copy the value
            error("The shell variable is too small to hold the shell"); //Exit with error
        strcpy(shell, ret_shell); //Copy the value from local variable to the "out" argument - shell
    }
    
    
    /*
    * Debugger method. Prints a line from stdin.
    */
    void print_line_from_stdin() {
        char toPrint[50] = {0};
        scanf("%50[^\n]\n", toPrint);
        fprintf(stderr, "%s\n", toPrint);
    }
    
    
    /*
    * Debugger method. Prints a line from the file provided.
    */
    void print_line_from_file(FILE* file) {
        char var[100] = {0};
        fscanf(file, "%100[^\n]\n", var);
        fprintf(stderr, "%s\n", var);
    }
    
    
    int main(int argc, char* args[]) {
    
    
        FILE *my_file = fopen(args[0], "r"); //Opens itself. This is where the bash script is stored.
    
    
        if(!my_file) //Obvious error check.
            error("Failed to open the file for reading!"); 
        
        int fd = fileno(my_file); //Get the file descriptor for this file "stream".
        
        seekFdToValue(fd); //Seek to the proper location. The beggining of the bash script which lives at the end of the file. (marked by magic constant)
    
    
        char shell[100] = {0}; //The shell which will be used to launch the script e.g. "/bin/bash"
        tryParseShebang(my_file, shell, 100);  //This is where the trouble starts. When no shebang is present, it works like a charm. with shebang the shell is returned properly, but problems start happening. 
        
        //fd = fileno(my_file); //Tried this for sanity after around 70 debug runs.
        
        /*
        * This may have to be dup3 to account for the close-on-exec, but I do not believe that is the problem, as though without shebang the program runs fine. 
        */
        int dup_res = dup2(fd, 0); //Map the file descriptor (now pointing to the begginging of the bash script) onto stdin, in preperation for rediraction into the shell. 
        
        if(dup_res == -1) //Check if the dup2() method failed. Never does.  
            error("Failed to redirect file into stdin");
            
        
        fprintf(stderr, "Shell: %s\n", shell); //Check to make sure the shell got read correctly (debugging)
        
        /*
        * This is the part that drives me crazy. The dup2 was sucessful, and when no shebang is present, stdin contains the lines of the bash script.
        * However, when a shebang is present, nothing is read from stdin, but data can be normaly read from the file stream.
        * This is insane as though the dup2 system call should have guaranteed that reading fd 0 would be synonymous to reading the file fd.
        */
        
        //These are just debug lines, idealy stdin is in place for the exec call
        print_line_from_stdin();
        //print_line_from_file(my_file);
        print_line_from_stdin();
        print_line_from_stdin();
        
        args[0] = shell; //Change the first argument to the shell.
        
        execv(shell, args); //Execute the shell, with the arguments, and with stdin redirected this way, it should run. (and it does when no shebang is present)
    
    
        return 0;
    }
    test.txt
    My little shell script which is meant to be ran through/inside the C program. It should be noted that everything works just fine without the first line:
    Code:
    #!/bin/bash
    
    echo "Line 1"
    echo "Line Two"
    echo "Line III"

  2. #2
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    You might want to look at the forum guidelines, [url=http://cboard.cprogramming.com/c-programming/announcement-full-forum-guidelines-read-before-posting.html]here[url]. Particularly point 6 related to hacking and cracking.

    While I realise that self-modifying executables can have benign purpose, they rarely do in practice.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  3. #3
    Registered User
    Join Date
    Jan 2015
    Posts
    2
    I do not intend to use this for anything but educational purposes, plus I am not trying to create something which is inherently dangerous or "hackish", all i am trying to do here is execute bash scripts on my own machine, with SUID. As though I am currently studying Computer and Network Security, I realize that this could potentially be used for "evil" purposes, however it is not a hack, as though it would not in anyway circumvent security. Someone with appropriate privileges can choose to grant SUID privileges, and this in no way shape or form represents an attempt at any sort of privilege escalation, just an honest investigation into a potentially safer use of shell scripts with SUID. I am given to understand that some linux distros do in fact use file descriptors to provide more security when executing shell scripts with higher privileges, all I am trying to do here is understand how that can be accomplished while learning more C programming. If you feel this is inappropriate, then I do apologize for the post, and will continue to struggle with this on my own.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Bash scripting redirection issue
    By gibson221 in forum Linux Programming
    Replies: 4
    Last Post: 12-22-2011, 01:28 PM
  2. Replies: 6
    Last Post: 11-30-2011, 12:49 AM
  3. Redirection Issue
    By Oblivious88 in forum C Programming
    Replies: 5
    Last Post: 04-08-2009, 03:13 AM
  4. Linux file redirection buffering issue !!!!
    By AirSeb in forum Linux Programming
    Replies: 8
    Last Post: 03-14-2009, 05:32 PM
  5. file descriptor redirection
    By msenthil in forum C Programming
    Replies: 15
    Last Post: 05-24-2007, 08:45 AM