chown(), lchown(), fchown() and Group Inheritance Rules in Linux

 

chown(), lchown(), fchown() and Group Inheritance Rules in Linux

 chown(), lchown(), fchown() and Group Inheritance Rules
3
chown variants
UID + GID
Ownership model
Security
setUID/GID rules

Why Does File Ownership Matter?

Every file on Linux is owned by a user (UID) and a group (GID). These two values, stored in the i-node, are the foundation of the Linux permission system. When you try to read, write, or execute a file, the kernel checks your UID/GID against the file’s UID/GID to decide what you’re allowed to do.

The chown() family of system calls lets privileged processes change file ownership. Understanding the rules around ownership — including how new files get their GID — is important for writing secure, correct programs.

Key Terms:

chown() lchown() fchown() UID GID effective user ID st_uid / st_gid set-group-ID dir grpid / nogrpid CAP_CHOWN

1. Ownership of New Files — Where Does the GID Come From?

When a process creates a new file:

User ID (UID) always comes from the process’s effective user ID. Simple.

⚠️ Group ID (GID) depends on the filesystem mount option and whether the parent directory has the set-group-ID bit set.

Mount option set-GID on parent dir? New file’s GID comes from
-o grpid (bsdgroups) Ignored Parent directory’s GID
-o nogrpid (default) Not set Process’s effective GID
-o nogrpid (default) Set (chmod g+s) Parent directory’s GID

Decision flow for new file’s GID:

New file created
grpid mount option?
YES → GID from parent dir
nogrpid + parent has g+s?
YES → GID from parent dir
NO → GID from process eGID
Practical use: Setting chmod g+s on a shared project directory ensures all files created in it automatically get the project group’s GID. Members of that group can then access all files regardless of which team member created them.

2. chown(), lchown(), fchown() — The Three Variants
Function File identified by Symlink behaviour
chown(path, uid, gid) pathname Follows symlink (changes target)
lchown(path, uid, gid) pathname Does NOT follow — changes the link itself
fchown(fd, uid, gid) open file descriptor N/A (fd)

To change only UID or only GID, pass -1 for the unchanged one:

chown("myfile", 1001, -1);   /* change only UID, keep GID */
chown("myfile", -1,  100);   /* change only GID, keep UID */
Security side effect: If you call chown() on a file, the kernel automatically turns OFF both the set-user-ID and set-group-ID bits. This prevents a user from making a file setUID-root after tricking root into changing ownership. This applies to unprivileged users; root-owned files behave the same.

3. Code Examples

Example 1: Read current file ownership

#include <stdio.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    struct stat sb;
    struct passwd *pw;
    struct group  *gr;

    if (argc != 2) { fprintf(stderr, "Usage: %s file\n", argv[0]); exit(1); }

    if (stat(argv[1], &sb) == -1) { perror("stat"); exit(1); }

    printf("File: %s\n", argv[1]);
    printf("UID: %d", sb.st_uid);

    /* Look up username from UID */
    pw = getpwuid(sb.st_uid);
    if (pw) printf(" (%s)", pw->pw_name);
    printf("\n");

    printf("GID: %d", sb.st_gid);

    /* Look up group name from GID */
    gr = getgrgid(sb.st_gid);
    if (gr) printf(" (%s)", gr->gr_name);
    printf("\n");

    return 0;
}

Example 2: chown() — change file owner (needs root or CAP_CHOWN)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    /* Change owner to UID 1001, keep existing GID */
    if (chown("myfile", 1001, -1) == -1) {
        perror("chown");
        exit(1);
    }
    printf("Owner changed to UID 1001\n");

    /* Change group to GID 100, keep existing owner */
    if (chown("myfile", -1, 100) == -1) {
        perror("chown (group)");
        exit(1);
    }
    printf("Group changed to GID 100\n");

    /* Change both at once */
    if (chown("myfile", 1001, 100) == -1) {
        perror("chown");
        exit(1);
    }
    return 0;
}

Example 3: lchown() — change ownership of the symlink itself

#include <stdio.h>
#include <unistd.h>  /* needs _BSD_SOURCE or _XOPEN_SOURCE 500 */
#include <stdlib.h>

int main(void) {
    /* Assume: ln -s /etc/passwd mylink */

    /* chown() would change /etc/passwd's owner (following the link) */
    /* lchown() changes the symlink entry itself */
    if (lchown("mylink", 1001, -1) == -1) {
        perror("lchown");
        exit(1);
    }
    printf("Symlink owner changed (target untouched)\n");
    return 0;
}
/* Compile: gcc -D_XOPEN_SOURCE=500 -o lchown_demo lchown_demo.c */

Example 4: fchown() — change ownership via file descriptor

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void) {
    int fd = open("myfile", O_RDWR);
    if (fd == -1) { perror("open"); exit(1); }

    /* Change group to GID 200 via fd */
    if (fchown(fd, -1, 200) == -1) {
        perror("fchown");
        close(fd);
        exit(1);
    }
    printf("Group changed via fd\n");
    close(fd);
    return 0;
}
/* fchown is safe against TOCTOU races because
   the fd is bound to the i-node, not the filename */

Example 5: Convert username to UID (utility function)

#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>

/* Convert username string to UID number, returns -1 on error */
uid_t username_to_uid(const char *name) {
    struct passwd *pw = getpwnam(name);
    if (!pw) return (uid_t)-1;
    return pw->pw_uid;
}

/* Convert group name string to GID number, returns -1 on error */
gid_t groupname_to_gid(const char *name) {
    struct group *gr = getgrnam(name);
    if (!gr) return (gid_t)-1;
    return gr->gr_gid;
}

int main(int argc, char *argv[]) {
    /* argv[1]=owner, argv[2]=group, argv[3]=file */
    if (argc != 4) { fprintf(stderr, "Usage: %s owner group file\n", argv[0]); exit(1); }

    uid_t uid = username_to_uid(argv[1]);
    gid_t gid = groupname_to_gid(argv[2]);

    if (uid == (uid_t)-1) { fprintf(stderr, "Unknown user: %s\n", argv[1]); exit(1); }
    if (gid == (gid_t)-1) { fprintf(stderr, "Unknown group: %s\n", argv[2]); exit(1); }

    if (chown(argv[3], uid, gid) == -1) {
        perror("chown");
        exit(1);
    }
    printf("Changed %s to %s:%s\n", argv[3], argv[1], argv[2]);
    return 0;
}

Example 6: Check if current process owns a file

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>

int process_owns_file(const char *path) {
    struct stat sb;
    if (stat(path, &sb) == -1) return 0;
    /* Compare file's UID with process's effective UID */
    return sb.st_uid == geteuid();
}

int main(void) {
    if (process_owns_file("/etc/passwd"))
        printf("I own /etc/passwd\n");
    else
        printf("I do NOT own /etc/passwd\n");

    return 0;
}

Example 7: Demonstrate set-GID directory (group inheritance)

/* Shell commands to demonstrate set-GID directory behaviour:
   (Run these as root or with proper permissions)

   # Create a shared project directory
   mkdir /shared/project
   chown root:devteam /shared/project
   chmod g+s /shared/project    <-- set-GID bit

   # Now ANY file created in /shared/project inherits GID 'devteam'
   # regardless of who creates it

   # Verify in C: */

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

void show_gid_of_new_file(const char *dir) {
    char path[256];
    struct stat sb;
    int fd;

    snprintf(path, sizeof(path), "%s/testfile", dir);
    fd = open(path, O_CREAT | O_WRONLY, 0644);
    if (fd == -1) { perror("open"); return; }
    close(fd);

    if (stat(path, &sb) == 0)
        printf("New file GID in %s: %d\n", dir, (int)sb.st_gid);
    unlink(path);
}

4. Who Can Call chown()? — Permission Rules
Who? Can change UID? Can change GID?
Privileged process (CAP_CHOWN) Yes — any UID Yes — any GID
Unprivileged, file owner No Yes — to any group they belong to
Unprivileged, not owner No No

5. Interview Questions

Q1 What is the difference between chown() and lchown()?

A chown() follows symbolic links and changes the ownership of the file the link points to. lchown() changes the ownership of the symbolic link itself, not the target file.

Q2 Can an unprivileged user change the owner (UID) of their own file?

A No. Only a privileged process with CAP_CHOWN capability (root) can change the UID of a file. An unprivileged user can only change the GID, and only to a group they are a member of.

Q3 What security measure does the kernel apply when chown() is called?

A The kernel automatically clears (turns off) the set-user-ID and set-group-ID permission bits when chown() is called. This prevents privilege escalation by creating a setUID-root executable.

Q4 When is set-GID NOT cleared by chown()? Name two cases.

A (1) When the group-execute permission bit is OFF — in that case set-GID is used for mandatory file locking, not execution. (2) When applied to a directory — set-GID controls group inheritance for new files, not execution.

Q5 How do you make all files in a directory automatically get the same group GID regardless of who creates them?

A Set the set-group-ID bit on the directory: chmod g+s /project_dir. On filesystems with nogrpid mount option (the default), new files will inherit the directory’s GID rather than the creator’s effective GID.

Q6 How do you change only the GID of a file without changing the UID?

A Pass -1 as the uid argument: chown("file", -1, new_gid); The -1 means “leave this ID unchanged”.

Next: File Permissions — chmod, umask, set-UID/GID, sticky bit

← Previous Next Topic →

Leave a Reply

Your email address will not be published. Required fields are marked *