summaryrefslogtreecommitdiff
path: root/fs/permissions
diff options
context:
space:
mode:
authorPaul Bütow <pbuetow@mimecast.com>2020-01-09 20:30:15 +0000
committerPaul Bütow <pbuetow@mimecast.com>2020-01-09 20:30:15 +0000
commit3755a9911ecb05886577095f2b8cc8b9e4066a3a (patch)
tree86e24bc466986cb5c9c6d167a918e6064defeafc /fs/permissions
Release of DTail v1.0.0v1.0.0
Diffstat (limited to 'fs/permissions')
-rw-r--r--fs/permissions/permission.go14
-rw-r--r--fs/permissions/permission_linux.c395
-rw-r--r--fs/permissions/permission_linux.go33
-rw-r--r--fs/permissions/permission_linux.h60
-rw-r--r--fs/permissions/permission_test.go112
5 files changed, 614 insertions, 0 deletions
diff --git a/fs/permissions/permission.go b/fs/permissions/permission.go
new file mode 100644
index 0000000..7d242f1
--- /dev/null
+++ b/fs/permissions/permission.go
@@ -0,0 +1,14 @@
+// +build !linux
+
+package permissions
+
+import (
+ "dtail/logger"
+)
+
+// ToRead is to check whether user has read permissions to a given file.
+func ToRead(user, filePath string) (bool, error) {
+ // Only implemented for Linux, always expect true
+ logger.Warn(user, filePath, "Not performing ACL check, not supported on this platform")
+ return true, nil
+}
diff --git a/fs/permissions/permission_linux.c b/fs/permissions/permission_linux.c
new file mode 100644
index 0000000..cd10525
--- /dev/null
+++ b/fs/permissions/permission_linux.c
@@ -0,0 +1,395 @@
+#include "permission_linux.h"
+
+#ifdef DEBUG
+void debug_print_checker(struct permission_checker *pc) {
+ fprintf(stderr, "DEBUG: user_name:%s (%d)\n",
+ pc->user_name, pc->uid);
+
+ fprintf(stderr, "DEBUG: ngids:%d\n", pc->ngids);
+ int j;
+ for (j = 0; j < pc->ngids; j++) {
+ fprintf(stderr, "DEBUG: %d", pc->gids[j]);
+ struct group *gr = getgrgid(pc->gids[j]);
+ if (gr != NULL)
+ fprintf(stderr, " (%s)", gr->gr_name);
+ fprintf(stderr, "\n");
+ }
+
+ fprintf(stderr, "DEBUG: file_path:%s (%d:%d)\n",
+ pc->file_path, pc->file_stat.st_uid, pc->file_stat.st_gid);
+}
+#endif // DEBUG
+
+int stat_file(struct permission_checker *pc) {
+ if (stat(pc->file_path, &pc->file_stat) != 0)
+ return -1;
+
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: File'%s' is owned by '%d:%d'\n",
+ pc->file_path, pc->file_stat.st_uid, pc->file_stat.st_gid);
+#endif
+
+ return 0;
+}
+
+int get_user_uid(struct permission_checker *pc) {
+ struct passwd *result = NULL;
+
+ size_t bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (bufsize == -1)
+ bufsize = 16384;
+
+ char *buf = malloc(bufsize);
+ if (buf == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: Unabel to allocate bufer while retrieving user '%s'\n", pc->user_name);
+#endif
+ return -1;
+ }
+
+ int rc = getpwnam_r(pc->user_name, &pc->pw, buf, bufsize, &result);
+
+ if (result == NULL) {
+#ifdef DEBUG
+ if (rc == 0) {
+ fprintf(stderr, "DEBUG: No user '%s' found\n", pc->user_name);
+ } else {
+ fprintf(stderr, "DEBUG: Unknown error while retrieving user '%s'\n", pc->user_name);
+ }
+#endif
+
+ free(buf);
+ return -1;
+ }
+
+ pc->uid = pc->pw.pw_uid;
+
+ free(buf);
+ return 0;
+}
+
+int get_user_groups(struct permission_checker *pc) {
+ // First assume we are in 10 groups max
+ pc->ngids = 10;
+ pc->gids = malloc(pc->ngids * sizeof(gid_t));
+
+ if (pc->gids == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: Unable to allocate space for gids.");
+#endif
+ return -1;
+ }
+
+ // Try so many times to load group list until it fits into group array.
+ while (getgrouplist(pc->user_name, pc->pw.pw_gid, pc->gids, &pc->ngids) == -1) {
+ // Too many groups, enlarge group array and try again
+ int newngids = pc->ngids + 100;
+ size_t newsize = newngids * sizeof(gid_t);
+
+ if (SIZE_MAX / newngids < sizeof(gid_t)) {
+ // Overflow
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: Overflow detected.");
+#endif
+ return -1;
+ }
+
+ gid_t *newgids = realloc(pc->gids, newsize);
+ if (newgids == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: Unable to allocate space for gids.");
+#endif
+ free(pc->gids);
+ return -1;
+ }
+
+ pc->gids = newgids;
+ pc->ngids = newngids;
+ }
+
+ return 0;
+}
+
+int is_member_of_group(struct permission_checker *pc, gid_t gid) {
+ int j;
+ for (j = 0; j < pc->ngids; j++)
+ if (pc->gids[j] == gid)
+ return 1;
+ return 0;
+}
+
+int check_acl_uid_matches(uid_t uid, acl_entry_t entry) {
+ int ret = -1;
+ uid_t *acl_uid = acl_get_qualifier(entry);
+ if (acl_uid == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: Unable to retrieve user uid from ACL entry");
+#endif
+ return -1;
+ }
+
+ ret = *acl_uid == uid ? 0 : -1;
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL user match?: %d <=> %d: %d\n", *acl_uid, uid, ret);
+#endif
+ acl_free(acl_uid);
+ return ret;
+}
+
+int check_acl_gid_matches(gid_t *gids, int ngids, acl_entry_t entry) {
+ int ret = -1;
+ gid_t *acl_gid = acl_get_qualifier(entry);
+ if (acl_gid == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: Unable to retrieve user uid from ACL entry");
+#endif
+ return -1;
+ }
+
+ int j;
+ for (j = 0; j < ngids; j++) {
+ if (*acl_gid == gids[j]) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: User is in group %d", *acl_gid);
+#endif
+ ret = 0;
+ break;
+ }
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL group match?: %d <=> ...: %d\n", *acl_gid, ret);
+#endif
+ acl_free(acl_gid);
+ return ret;
+}
+
+int check_acl(struct permission_checker *pc, const int flag) {
+ // By default user has no read perm.
+ int has_read_perm = 0;
+
+ // By default mask tells that there are read perm. However in order to have
+ // read permissions both, has_read_perm and mask_allows_read_access must be 1!
+ int mask_allows_read_access = 1;
+
+ acl_type_t type = ACL_TYPE_ACCESS;
+ acl_t acl = acl_get_file(pc->file_path, type);
+
+ if (acl == NULL)
+ // Unable to retrieve ACL.
+ return -1;
+
+ // Walk through each entry of this ACL.
+ int id;
+ for (id = ACL_FIRST_ENTRY; ; id = ACL_NEXT_ENTRY) {
+ acl_entry_t entry;
+ if (acl_get_entry(acl, id, &entry) != 1)
+ // No more ACL entries.
+ break;
+
+ acl_tag_t tag;
+ if (acl_get_tag_type(entry, &tag) == -1)
+ // Unable to retrieve ACL tag.
+ return -1;
+
+ switch (tag) {
+ case ACL_USER_OBJ:
+ if (flag == GROUP_CHECK)
+ continue;
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL_USER_OBJ\n");
+#endif
+ // Ignore this ACL entry if user is not owner of file.
+ if (pc->uid != pc->file_stat.st_uid)
+ continue;
+ break;
+ case ACL_USER:
+ if (flag == GROUP_CHECK)
+ continue;
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL_USER\n");
+#endif
+ // Ignore this ACL entry if uid does not match.
+ if (check_acl_uid_matches(pc->uid, entry) != 0)
+ continue;
+ break;
+ case ACL_GROUP_OBJ:
+ if (flag == USER_CHECK)
+ continue;
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL_GROUP_OBJ\n");
+#endif
+ // Ignore ACL entry if user is not in group of file.
+ if (!is_member_of_group(pc, pc->file_stat.st_gid))
+ continue;
+ break;
+ case ACL_GROUP:
+ if (flag == USER_CHECK)
+ continue;
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL_GROUP\n");
+#endif
+ // Ignore ACL entry if user is not in group of entry.
+ if (check_acl_gid_matches(pc->gids, pc->ngids, entry) != 0)
+ continue;
+ break;
+ case ACL_OTHER:
+ if (flag == GROUP_CHECK)
+ continue;
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL_OTHER\n");
+#endif
+ break;
+ case ACL_MASK:
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL_MASK\n");
+#endif
+ break;
+ default:
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: Unknown ACL tag\n");
+#endif
+ return -1;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: Retrieving permset\n");
+#endif
+ acl_permset_t permset;
+ int permission;
+ if (acl_get_permset(entry, &permset) == -1)
+ // Unable to retrieve permset.
+ return -1;
+
+ if ((permission = acl_get_perm(permset, ACL_READ)) == -1)
+ // Unable to retrieve permset value.
+ return -1;
+
+ if (permission == 1 && tag != ACL_MASK) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL says user has permission to read file.\n");
+#endif
+ has_read_perm = 1;
+ } else if (permission == 0 && tag == ACL_MASK) {
+ // Mask says that there are no permissions to read.
+ mask_allows_read_access = 0;
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL mask says no permission to read file.\n");
+#endif
+ }
+ }
+
+ if (has_read_perm && mask_allows_read_access) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL end result: User has permission to read file.\n");
+#endif
+ return 1;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: ACL end result: User has no permission to read file.\n");
+#endif
+ return 0;
+}
+
+int check_traditional(struct permission_checker *pc, const int flag) {
+ mode_t mode = pc->file_stat.st_mode;
+ uid_t uid = pc->file_stat.st_uid;
+ gid_t gid = pc->file_stat.st_gid;
+
+ if (flag == USER_CHECK && (mode & S_IROTH)) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: Others can read file '%s'\n",
+ pc->file_path);
+#endif
+ return 1;
+
+ } else if (flag == USER_CHECK && (mode & S_IRUSR) && uid == pc->uid) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: User '%s' can read file '%s'\n",
+ pc->user_name, pc->file_path);
+#endif
+ return 1;
+
+ } else if (flag == GROUP_CHECK && (mode & S_IRGRP) && is_member_of_group(pc, gid)) {
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: User's '%s' group can read file '%s'\n",
+ pc->user_name, pc->file_path);
+#endif
+ return 1;
+ }
+
+ return 0;
+}
+
+int permission_to_read(char* user_name, char *file_path) {
+ int rc = -1;
+
+#ifdef DEBUG
+ fprintf(stderr, "DEBUG: User check '%s' for file '%s'\n", user_name, file_path);
+#endif
+ struct permission_checker pc = {
+ .user_name = user_name,
+ .gids = NULL,
+ .ngids = 0,
+ .file_path = file_path,
+ };
+
+ // Gather user's UID.
+ if ((rc = get_user_uid(&pc)) == -1)
+ // Could not retrieve UID.
+ goto cleanup;
+
+ // Gather file owner (user and group).
+ if ((rc = stat_file(&pc)) == -1)
+ // Could not stat file.
+ goto cleanup;
+
+ // Check whether there is an ACL entry which would allow the user
+ // to read the file. Don't check for any groups yet. The issue with
+ // groups is that it can be very slow to retrieve the list of groups
+ // of a specific user when done via a remote LDAP server!
+ if ((rc = check_acl(&pc, USER_CHECK)) == 1)
+ // Yes, has permissions.
+ goto cleanup;
+
+ // Check whether ACLs of file could be retrieved.
+ if (rc == -1) {
+ if (errno != ENOTSUP)
+ // Unknown error.
+ goto cleanup;
+
+ // File system does not support ACLs.
+ // Fallback to traditional permissions.
+ if ((rc = check_traditional(&pc, USER_CHECK)) == 1)
+ // Yes, has traditional permissions.
+ goto cleanup;
+
+ if ((rc = get_user_groups(&pc)) == -1)
+ // Can not retrieve user's groups.
+ goto cleanup;
+
+ rc = check_traditional(&pc, GROUP_CHECK);
+ goto cleanup;
+ }
+
+ if ((rc = get_user_groups(&pc)) == -1)
+ // Can not retrieve use'r groups.
+ goto cleanup;
+
+ // Check whether there is an ACL entry which would allow any of the
+ // user's groups to read the file.
+ rc = check_acl(&pc, GROUP_CHECK);
+
+cleanup:
+#ifdef DEBUG
+ debug_print_checker(&pc);
+#endif
+
+ if (pc.ngids)
+ free(pc.gids);
+
+ return rc;
+}
+
+// vim: set tabstop=8 softtabstop=0 expandtab shiftwidth=4 smarttab
diff --git a/fs/permissions/permission_linux.go b/fs/permissions/permission_linux.go
new file mode 100644
index 0000000..feae729
--- /dev/null
+++ b/fs/permissions/permission_linux.go
@@ -0,0 +1,33 @@
+package permissions
+
+/*
+#include "permission_linux.h"
+#cgo LDFLAGS: -L. -lacl
+*/
+import "C"
+
+import (
+ "errors"
+ "unsafe"
+)
+
+// To check whether user has Linux file system permissions to read a given file.
+func ToRead(user, filePath string) (bool, error) {
+ cUser := C.CString(user)
+ cFilePath := C.CString(filePath)
+
+ defer C.free(unsafe.Pointer(cUser))
+ defer C.free(unsafe.Pointer(cFilePath))
+
+ cOk, err := C.permission_to_read(cUser, cFilePath)
+ if cOk == 1 {
+ return true, nil
+ }
+
+ if err != nil {
+ // err contains errno message
+ return false, err
+ }
+
+ return false, errors.New("User without permission to read file")
+}
diff --git a/fs/permissions/permission_linux.h b/fs/permissions/permission_linux.h
new file mode 100644
index 0000000..a2c266e
--- /dev/null
+++ b/fs/permissions/permission_linux.h
@@ -0,0 +1,60 @@
+#ifndef PERMISSION_LINUX_H
+#define PERMISSION_LINUX_H
+
+#include <acl/libacl.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/acl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+//#define DEBUG
+#define USER_CHECK 0
+#define GROUP_CHECK 1
+
+struct permission_checker {
+ char *user_name;
+ uid_t uid;
+ gid_t *gids;
+ int ngids;
+ char *file_path;
+ struct stat file_stat;
+ struct passwd pw;
+};
+
+
+#ifdef DEBUG
+// Print out permission_checker struct.
+void debug_print_checker(struct permission_checker *pc);
+#endif
+
+// Stat a given file to retrieve traditional UNIX permissions.
+int stat_file(struct permission_checker *pc);
+
+// Retrieve UID of user.
+int get_user_uid(struct permission_checker *pc);
+
+// Retrieve all groups of the user.
+int get_user_groups(struct permission_checker *pc);
+
+// Check whether user is member of a group or not.
+int is_member_of_group(struct permission_checker *pc, gid_t gid);
+
+// Check whether user can read file according Linux ACLs.
+// As flag use either USER_CHECK or GROUP_CHECK.
+int check_acl(struct permission_checker *pc, const int flag);
+
+// Check whether user has permissions to read file according traditional
+// UNIX permissions. As flag use either USER_CHECK or GROUP_CHECK.
+int check_traditional(struct permission_checker *pc, const int flag);
+
+// Returns 1 if user has permission to read file.
+// Returns <0 on error and returns 0 if no permissions.
+int permission_to_read(char* user, char *file_path);
+
+#endif // PERMISSION_LINUX_H
diff --git a/fs/permissions/permission_test.go b/fs/permissions/permission_test.go
new file mode 100644
index 0000000..d415ac2
--- /dev/null
+++ b/fs/permissions/permission_test.go
@@ -0,0 +1,112 @@
+// +build linux
+
+package permissions
+
+import (
+ "os"
+ "os/exec"
+ "os/user"
+ "strings"
+ "testing"
+)
+
+const (
+ setfacl string = "/usr/bin/setfacl"
+ file string = "/tmp/acltest"
+)
+
+func TestLinuxACL(t *testing.T) {
+ setfacl := "/usr/bin/setfacl"
+ file := "/tmp/acltest"
+
+ // Delete file if it exists.
+ if _, err := os.Stat(file); err == nil {
+ os.Remove(file)
+ }
+
+ f, err := os.Create(file)
+ if err != nil {
+ t.Errorf("%v", err)
+ }
+ defer func() {
+ f.Close()
+ //os.Remove(file)
+ }()
+
+ user, err := user.Current()
+ if err != nil {
+ t.Errorf("Unable to retrieve current user: %v", err)
+ }
+
+ // Test 1: Remove all permissions and perform a permission check
+ cmd := exec.Command(setfacl, "-b", "-m", "u::---,g::---,o::---", file)
+ if err := cmd.Run(); err != nil {
+ t.Errorf("%s -> %v", strings.Join(cmd.Args, " "), err)
+ }
+ if ok, _ := ToRead(user.Username, file); ok {
+ t.Errorf("Didn't expect permissions to read file!")
+ }
+
+ // Test 2: Add read permission to file owner
+ cmd = exec.Command(setfacl, "-b", "-m", "u::r--,g::---,o::---", file)
+ if err := cmd.Run(); err != nil {
+ t.Errorf("%s -> %v", strings.Join(cmd.Args, " "), err)
+ }
+ if ok, err := ToRead(user.Username, file); !ok {
+ t.Errorf("Expected permissions to read file: %v", err)
+ }
+
+ // Test 3: Add read permission to file group
+ cmd = exec.Command(setfacl, "-b", "-m", "u::---,g::r--,o::---", file)
+ if err := cmd.Run(); err != nil {
+ t.Errorf("%s -> %v", strings.Join(cmd.Args, " "), err)
+ }
+ if ok, err := ToRead(user.Username, file); !ok {
+ t.Errorf("Expected permissions to read file: %v", err)
+ }
+
+ // Test 4: Add read permission to others
+ cmd = exec.Command(setfacl, "-b", "-m", "u::---,g::---,o::r--", file)
+ if err := cmd.Run(); err != nil {
+ t.Errorf("%s -> %v", strings.Join(cmd.Args, " "), err)
+ }
+
+ if ok, err := ToRead(user.Username, file); !ok {
+ t.Errorf("Expected permissions to read file: %v", err)
+ }
+
+ // Test 5: Remove read permission from mask
+ cmd = exec.Command(setfacl, "-m", "m::---", file)
+ if err := cmd.Run(); err != nil {
+ t.Errorf("%s -> %v", strings.Join(cmd.Args, " "), err)
+ }
+ if ok, _ := ToRead(user.Username, file); ok {
+ t.Errorf("Didn't expect permissions to read file!")
+ }
+ cmd = exec.Command(setfacl, "-m", "m::r--", file)
+ if err := cmd.Run(); err != nil {
+ t.Errorf("%s -> %v", strings.Join(cmd.Args, " "), err)
+ }
+
+ // Test 6: Add read permission to specific group
+ cmd = exec.Command(setfacl, "-b", "-m", "u::---,g:"+user.Username+":r--,o::---", file)
+ if err := cmd.Run(); err != nil {
+ t.Errorf("%s -> %v", strings.Join(cmd.Args, " "), err)
+ }
+ if ok, err := ToRead(user.Username, file); !ok {
+ t.Errorf("Expected permissions to read file for user %v: %v", user.Username, err)
+ }
+
+ // Test 7: Remove all permissions but mask
+ cmd = exec.Command(setfacl, "-b", "-m", "u::---,g::---,o::---", file)
+ if err := cmd.Run(); err != nil {
+ t.Errorf("%s -> %v", strings.Join(cmd.Args, " "), err)
+ }
+ cmd = exec.Command(setfacl, "-m", "m::r--", file)
+ if err := cmd.Run(); err != nil {
+ t.Errorf("%s -> %v", strings.Join(cmd.Args, " "), err)
+ }
+ if ok, _ := ToRead(user.Username, file); ok {
+ t.Errorf("Didn't expect permissions to read file!")
+ }
+}