menios
menios copied to clipboard
Implement chmod and chown syscalls for file permissions
Goal
Implement chmod() and chown() syscalls to allow changing file permissions and ownership, enabling proper POSIX permission management on ext2 and other filesystems that support it.
Context
With ext2 filesystem support (#227) and binary migration to ext2 (#229), meniOS will have filesystems that support full POSIX permissions and ownership. We need syscalls to:
- Change file permission bits (rwx for user/group/other)
- Change file ownership (UID and GID)
- Support both symbolic and numeric permission modes
- Provide userspace utilities (chmod, chown)
These are essential for:
- Setting proper permissions on system binaries (755)
- Configuring file security
- Multi-user system support (future)
- Standard UNIX file management
Definition of Done
- chmod() syscall: Change file permission bits
- fchmod() syscall: Change permissions via file descriptor
- chown() syscall: Change file owner and group
- fchown() syscall: Change ownership via file descriptor
- lchown() syscall: Change ownership of symbolic link itself
- Permission validation: Check user permissions before allowing changes
- Userspace utilities: /bin/chmod and /bin/chown programs
Syscall Interface
chmod() Family
// Change file mode (permissions)
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags);
// Permission bits
#define S_ISUID 04000 // Set UID bit
#define S_ISGID 02000 // Set GID bit
#define S_ISVTX 01000 // Sticky bit
#define S_IRUSR 00400 // User read
#define S_IWUSR 00200 // User write
#define S_IXUSR 00100 // User execute
#define S_IRGRP 00040 // Group read
#define S_IWGRP 00020 // Group write
#define S_IXGRP 00010 // Group execute
#define S_IROTH 00004 // Others read
#define S_IWOTH 00002 // Others write
#define S_IXOTH 00001 // Others execute
// Convenience macros
#define S_IRWXU 00700 // User rwx
#define S_IRWXG 00070 // Group rwx
#define S_IRWXO 00007 // Others rwx
// Example usage
chmod("/bin/mosh", 0755); // rwxr-xr-x
chmod("/etc/passwd", 0644); // rw-r--r--
chmod("/tmp", 01777); // rwxrwxrwt (sticky)
chown() Family
// Change file ownership
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner, gid_t group);
int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags);
// Special values
#define UID_UNCHANGED ((uid_t)-1) // Don't change UID
#define GID_UNCHANGED ((gid_t)-1) // Don't change GID
// Example usage
chown("/bin/mosh", 0, 0); // root:root
chown("/home/user/file", 1000, 1000); // user:user
chown("/tmp/file", -1, 100); // Change group only
Kernel Implementation
chmod() Syscall
// Syscall: chmod(pathname, mode)
long sys_chmod(const char __user *pathname, mode_t mode) {
struct dentry *dentry;
struct inode *inode;
int ret;
// Lookup file
dentry = user_path_lookup(pathname, LOOKUP_FOLLOW);
if (IS_ERR(dentry)) {
return PTR_ERR(dentry);
}
inode = dentry->d_inode;
// Check permissions
ret = inode_permission(inode, MAY_WRITE);
if (ret < 0) {
goto out;
}
// Check if owner or root
if (current->uid != inode->i_uid && current->uid != 0) {
ret = -EPERM;
goto out;
}
// Perform chmod
ret = inode_setattr(inode, mode, -1, -1);
out:
dput(dentry);
return ret;
}
// Syscall: fchmod(fd, mode)
long sys_fchmod(int fd, mode_t mode) {
struct file *file;
struct inode *inode;
int ret;
file = fget(fd);
if (!file) {
return -EBADF;
}
inode = file->f_dentry->d_inode;
// Check permissions
if (current->uid != inode->i_uid && current->uid != 0) {
ret = -EPERM;
goto out;
}
ret = inode_setattr(inode, mode, -1, -1);
out:
fput(file);
return ret;
}
chown() Syscall
// Syscall: chown(pathname, uid, gid)
long sys_chown(const char __user *pathname, uid_t uid, gid_t gid) {
return sys_chownat(AT_FDCWD, pathname, uid, gid, 0);
}
// Syscall: lchown(pathname, uid, gid) - don't follow symlinks
long sys_lchown(const char __user *pathname, uid_t uid, gid_t gid) {
return sys_chownat(AT_FDCWD, pathname, uid, gid, AT_SYMLINK_NOFOLLOW);
}
// Syscall: fchown(fd, uid, gid)
long sys_fchown(int fd, uid_t uid, gid_t gid) {
struct file *file;
struct inode *inode;
int ret;
file = fget(fd);
if (!file) {
return -EBADF;
}
inode = file->f_dentry->d_inode;
ret = chown_common(inode, uid, gid);
fput(file);
return ret;
}
// Common chown implementation
int chown_common(struct inode *inode, uid_t uid, gid_t gid) {
uid_t old_uid = inode->i_uid;
gid_t old_gid = inode->i_gid;
// Determine new UID
if (uid == (uid_t)-1) {
uid = old_uid;
}
// Determine new GID
if (gid == (gid_t)-1) {
gid = old_gid;
}
// Permission checks
if (current->uid != 0) {
// Only root can change ownership arbitrarily
if (uid != old_uid || (gid != old_gid && !in_group_p(gid))) {
return -EPERM;
}
}
// Perform chown
return inode_setattr(inode, -1, uid, gid);
}
inode_setattr() - Change Inode Attributes
// Update inode attributes (mode, uid, gid)
int inode_setattr(struct inode *inode, mode_t mode, uid_t uid, gid_t gid) {
struct iattr attr;
int ret;
memset(&attr, 0, sizeof(attr));
// Set mode if specified
if (mode != (mode_t)-1) {
attr.ia_valid |= ATTR_MODE;
attr.ia_mode = mode & 07777; // Only permission bits
// Clear SUID/SGID on non-privileged chmod
if (current->uid != 0) {
attr.ia_mode &= ~(S_ISUID | S_ISGID);
}
}
// Set UID if specified
if (uid != (uid_t)-1) {
attr.ia_valid |= ATTR_UID;
attr.ia_uid = uid;
}
// Set GID if specified
if (gid != (gid_t)-1) {
attr.ia_valid |= ATTR_GID;
attr.ia_gid = gid;
}
// Update ctime
attr.ia_valid |= ATTR_CTIME;
attr.ia_ctime = current_time();
// Call filesystem-specific setattr
if (inode->i_op && inode->i_op->setattr) {
ret = inode->i_op->setattr(inode, &attr);
} else {
ret = simple_setattr(inode, &attr);
}
// Mark inode dirty
if (ret == 0) {
mark_inode_dirty(inode);
}
return ret;
}
// Simple setattr for basic filesystems
int simple_setattr(struct inode *inode, struct iattr *attr) {
if (attr->ia_valid & ATTR_MODE) {
inode->i_mode = (inode->i_mode & S_IFMT) | (attr->ia_mode & 07777);
}
if (attr->ia_valid & ATTR_UID) {
inode->i_uid = attr->ia_uid;
}
if (attr->ia_valid & ATTR_GID) {
inode->i_gid = attr->ia_gid;
}
if (attr->ia_valid & ATTR_CTIME) {
inode->i_ctime = attr->ia_ctime;
}
return 0;
}
Permission Checking
// Check if current user can modify file
int may_chmod(struct inode *inode) {
// Owner can always chmod
if (current->uid == inode->i_uid) {
return 0;
}
// Root can always chmod
if (current->uid == 0) {
return 0;
}
return -EPERM;
}
// Check if current user can chown
int may_chown(struct inode *inode, uid_t uid, gid_t gid) {
// Root can always chown
if (current->uid == 0) {
return 0;
}
// Owner can change group to one they're in
if (current->uid == inode->i_uid && uid == inode->i_uid) {
if (gid == inode->i_gid || in_group_p(gid)) {
return 0;
}
}
return -EPERM;
}
// Check if user is in group
bool in_group_p(gid_t gid) {
// Check primary group
if (current->gid == gid) {
return true;
}
// Check supplementary groups
for (int i = 0; i < current->ngroups; i++) {
if (current->groups[i] == gid) {
return true;
}
}
return false;
}
Filesystem Integration
ext2 setattr Implementation
// ext2-specific setattr
int ext2_setattr(struct inode *inode, struct iattr *attr) {
struct ext2_inode *raw_inode;
int ret;
// Validate attributes
ret = inode_change_ok(inode, attr);
if (ret < 0) {
return ret;
}
// Update in-memory inode
ret = simple_setattr(inode, attr);
if (ret < 0) {
return ret;
}
// Update on-disk inode
ret = ext2_inode_read(inode->i_sb, inode->i_ino, raw_inode);
if (ret < 0) {
return ret;
}
if (attr->ia_valid & ATTR_MODE) {
raw_inode->i_mode = (raw_inode->i_mode & 0xF000) | (attr->ia_mode & 0x0FFF);
}
if (attr->ia_valid & ATTR_UID) {
raw_inode->i_uid = attr->ia_uid;
}
if (attr->ia_valid & ATTR_GID) {
raw_inode->i_gid = attr->ia_gid;
}
if (attr->ia_valid & ATTR_CTIME) {
raw_inode->i_ctime = attr->ia_ctime;
}
ret = ext2_inode_write(inode->i_sb, inode->i_ino, raw_inode);
return ret;
}
Userspace Utilities
/bin/chmod Program
// chmod utility
int main(int argc, char **argv) {
mode_t mode;
bool symbolic = false;
if (argc < 3) {
fprintf(stderr, "Usage: chmod mode file...\n");
return 1;
}
// Parse mode (numeric or symbolic)
if (isdigit(argv[1][0])) {
// Numeric mode: chmod 755 file
mode = strtol(argv[1], NULL, 8);
} else {
// Symbolic mode: chmod u+x file
symbolic = true;
// TODO: parse symbolic mode
fprintf(stderr, "Symbolic mode not yet implemented\n");
return 1;
}
// Apply to all files
for (int i = 2; i < argc; i++) {
if (chmod(argv[i], mode) < 0) {
perror(argv[i]);
continue;
}
printf("Changed mode of '%s' to %04o\n", argv[i], mode);
}
return 0;
}
/bin/chown Program
// chown utility
int main(int argc, char **argv) {
uid_t uid = -1;
gid_t gid = -1;
char *owner_spec;
char *colon;
if (argc < 3) {
fprintf(stderr, "Usage: chown [user][:group] file...\n");
return 1;
}
owner_spec = argv[1];
// Parse user:group
colon = strchr(owner_spec, ':');
if (colon) {
*colon = '\0';
if (colon[1]) {
gid = parse_gid(colon + 1);
}
}
if (owner_spec[0]) {
uid = parse_uid(owner_spec);
}
// Apply to all files
for (int i = 2; i < argc; i++) {
if (chown(argv[i], uid, gid) < 0) {
perror(argv[i]);
continue;
}
printf("Changed ownership of '%s'\n", argv[i]);
}
return 0;
}
// Parse UID from username or numeric ID
uid_t parse_uid(const char *user) {
// Try numeric first
if (isdigit(user[0])) {
return atoi(user);
}
// TODO: Look up username in /etc/passwd
return 0; // Default to root for now
}
Testing Strategy
- Test chmod with various permission modes
- Verify only owner or root can chmod
- Test fchmod with file descriptors
- Test chown with various UID/GID combinations
- Verify permission checks work correctly
- Test SUID/SGID bit handling
- Test sticky bit on directories
- Verify changes persist after remount
Security Considerations
- Only owner or root can chmod
- Only root can change file ownership arbitrarily
- Owner can change group to one they're in
- Clear SUID/SGID when non-root changes permissions
- Validate mode bits (only use 07777 bits)
- Prevent privilege escalation via chmod/chown
Dependencies
- VFS layer: #65 (already complete)
- ext2 filesystem: #227 (for permission support)
- User/Group management: Basic UID/GID support (current task)
Integration Points
- Binary migration: #229 (set permissions on /bin files)
- Filesystem operations: ext2 setattr implementation
- Init process: Set initial file permissions
Files to Create/Modify
- src/kernel/syscall/chmod.c - chmod syscalls
- src/kernel/syscall/chown.c - chown syscalls
- src/kernel/fs/attr.c - Attribute management
- src/usermode/chmod.c - chmod utility
- src/usermode/chown.c - chown utility
- include/uapi/stat.h - Permission bit definitions
- test/test_chmod.c - Permission tests
Deliverables
- chmod()/fchmod() syscalls
- chown()/fchown()/lchown() syscalls
- Permission validation
- ext2 setattr implementation
- /bin/chmod utility
- /bin/chown utility
- Test suite
- Documentation
Related Issues
- #229 - Binary migration to ext2 (needs chmod to set 755)
- #227 - ext2 filesystem support
- #65 - VFS layer