Chapter 8 Question 1

Michael Kerrisk - Linux Programming Interface

Question

When we execute the following code, we find that it displays the same number twice, even though the two users have different IDs in the password file. Why is this?


printf("%ld %ld\n", (long) (getpwnam("avr")->pw_uid),
                    (long) (getpwnam("tsr")->pw_uid));

Implementation

Note: I am not a C developer and I provide this example as a best effort. This code wasn't peer reviewed and is intended for learning purposes only.


/**
 * Chapter Eight Question One Michael Kerrisk
 * Linux Programming Interface
 * When we execute the following code, we find that it displays the same number twice, even though the two users
 * have different IDs in the password file. Why is this?
 *
 *     printf("%ld %ld\n",
 *          (long) (getpwnam("avr")->pw_uid),
 *          (long) (getpwnam("tsr")->pw_uid));
 *
 * The system password file, /etc/passwd contains one line for each user account on the system each line is composed
 * of seven fields seperated by colons.
 *
 * UID is the numeric ID for the user, if the this field has a value 0 then this account has superuser privilege.
 * There is normally one such account with the login name root.
 *
 * The shadow password file, /etc/shadow contains all the sensitive information about a user and can only be read
 * by privileged users.
 *
 * I did this lab on a mac so you need to figure out how to get the equivalent files which can be found here:
 *
 * sudo su -
 * cd /var/db/dslocal/nodes/Default/users
 * ls -l | grep -v "_"
 *
 * The above will give you a list of all the users plist files so you can modify to your own use case.  The book
 * assumes you have those users in your shadow file which you may / may not have. Each user has their own shadow file
 * on mac.
 */
#include <stdio.h>
#include <pwd.h>
#include <errno.h>
#include <string.h>

/**
 * main()
 *
 * @return 0
 */
int main() {
    /*
     * getpwnam() function returns a pointer to a structure containing the broken-outfields of the record in the
     * password database, the struct is of type passwd. One of the fields is the UID field.
     */
    fprintf(stdout, "Getting user one and user two");
    struct passwd *userOne = getpwnam("mtrottie");
    struct passwd *userTwo = getpwnam("nx");

    if (userOne != NULL && userTwo != NULL) {
        fprintf(stdout, "userOne and userTwo are not null\n");
        if (0 == errno) {
            fprintf(stdout, "UserOne: %ld\nUserTwo: %ld\n", (long) userOne->pw_uid, (long) userTwo->pw_uid);
        } else {
            fprintf(stdout, "%s", strerror(errno));
            return -1;
        }
    } else {
        fprintf(stdout, "%s", strerror(errno));
        return -1;
    }


    /*
     * I believe this question is trying to get at the fact that getpwnam funciton returns a pointer to a "static"
     * structure, however, it appears that it is no longer static.
     */
    printf("%ld %ld\n",
           (long) (getpwnam("root")->pw_uid),
           (long) (getpwnam("admin")->pw_uid));

    // To prove this I can point the pointers.
    printf("Pointer for getpwnam One:%p\nPointer for getpwnam Two:%p\n",
           getpwnam("root"),
           getpwnam("admin"));
}

Mac Version Answer

On mac the passwd structure isn't static because each user has its own version of /etc/shadow that is in a different direction. The location is at: /var/db/dslocal/nodes/Default/users. Because of the structure not being static it will return different pointers to different passwd structures therefore it won't print the same UID.

Ubuntu Answer

You can see the output for an ubuntu run of the same program on Ubuntu


mike@phobos:~/CLionProjects/chapterEightQuestionOne/cmake-build-debug$ ./chapterEightQuestionOne 
Getting user one and user two.
userOne and userTwo are not null
UserOne: 2
UserTwo: 2
0 2
Pointer for getpwnam One:0x7f3806013ee0
Pointer for getpwnam Two:0x7f3806013ee0

Assembly of Implementation

I used a smaller version of the above implementation to make reading assembler easier. See below:


#include <stdio.h>
#include <pwd.h>

int main() {
    printf("%ld %ld\n",
           (long) (getpwnam("root")->pw_uid),
           (long) (getpwnam("bin")->pw_uid));
}

The output of the above on Ubuntu


mike@phobos:~/CLionProjects/chapterEightSupplimental/cmake-build-debug$ ./chapterEightSupplimental 
0 2

In order to get the full assembly of this program you can compile it like so: gcc -S main.c with this you can see the assembly instructions and the various registers.

Data Segment

The LCX section contains the data segment of the program which is read only as it would be shared with other processes that use this program code.


mike@phobos:~/CLionProjects/chapterEightSupplimental$ cat main.s 
        .file   "main.c"
        .text
        .section        .rodata
.LC0:
        .string "bin"
.LC1:
        .string "root"
.LC2:
        .string "%ld %ld\n"
        .text
        .globl  main

Main assembly

You can see the remaining assembly below:


main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        pushq   %rbx
        subq    $8, %rsp
        .cfi_offset 3, -24
        leaq    .LC0(%rip), %rdi
        call    getpwnam@PLT
        movl    16(%rax), %eax
        movl    %eax, %ebx
        leaq    .LC1(%rip), %rdi
        call    getpwnam@PLT
        movl    16(%rax), %eax
        movl    %eax, %eax
        movq    %rbx, %rdx
        movq    %rax, %rsi
        leaq    .LC2(%rip), %rdi
        movl    $0, %eax
        call    printf@PLT
        movl    $0, %eax
        addq    $8, %rsp
        popq    %rbx
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 7.3.0-16ubuntu3) 7.3.0"
        .section        .note.GNU-stack,"",@progbits

Need to read up more on assembly, but from what it looks like to me like the initial call to getpwnam() stores the pointer in %rax register and moves it to the %rsi register. The second call to getpwnam() moves the pointer to %rax again and this is movied then into %rdi. Then the print call happens printing two different values.