menios icon indicating copy to clipboard operation
menios copied to clipboard

Implement chmod and chown syscalls for file permissions

Open pbalduino opened this issue 3 months ago • 0 comments

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

pbalduino avatar Oct 09 '25 03:10 pbalduino