chown variants
Ownership model
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:
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:
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.| 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 */
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.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);
}
| 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 |
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
