acl-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] setfacl is not atomic and corresponding repair solution


From: dai.shixin
Subject: [PATCH] setfacl is not atomic and corresponding repair solution
Date: Tue, 13 Aug 2024 15:40:26 +0800 (CST)

From:  dai.shixin@zte.com.cn


Hi,

We have found a issue about setfacl, the detail is as followed,

1. Issue description: we update ACL rules for a same directory when multiple containers launched, but the ACL rules may be lost occasionally. However, no error is reported by setfacl.For example, after running command `setfacl -R -m u:1234:rwx /mnt`, the command is executed successfully, but the /mnt directory does not have the ACL rule for user 1234.


2. Cause analysis: It is found that the read/write process of the ACL rule set by setfacl is not atomic. For example, setfacl instance A will overwrites the rule saved by instance B.

setfacl instance A:      get_acl()                               set_acl()

setfacl instance B:                 get_acl()           set_acl()


3. Recommended solution: Using the flock mechanism provided by kernel to guarantee the read/write process is atomic.The approximate solution is described as follows:

int

do_set(

const char *path_p,

const struct stat *st,

int walk_flags,

void *arg)

{

........

+   /* skip setting if the rule is redundant */

+ if (do_set_need_skip()):

    return;


+   /* add lock to guarantee the atomic read-write process */

+ flock(path_p);

RETRIEVE_ACL(cmd->c_type); get acl rules

.............


if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) != 0) { set acl rules

+   unlock();

.......

}


Signed-off-by: dai.shixin@zte.com.cn

Reviewed-by: ran.xiaokai@zte.com.cn

Reviewed-by: zhang.wenya1@zte.com.cn

Reviewed-by: yang.yang29@zte.com.cn

Reviewed-by: si.hao@zte.com.cn

---

 tools/do_set.c | 264 +++++++++++++++++++++++++++++++++++++++++++++++++

 1 file changed, 264 insertions(+)


diff --git a/tools/do_set.c b/tools/do_set.c

index 696689c..d083f8b 100644

--- a/tools/do_set.c

+++ b/tools/do_set.c

@@ -38,6 +38,7 @@

 #include "do_set.h"

 #include "parse.h"

 #include "walk_tree.h"

+#include <sys/file.h>

 

 

 static acl_entry_t

@@ -251,6 +252,223 @@ remove_extended_entries(

  goto fail; \

  } while(0)

 

+int

+do_set_need_skip(

+ const char *path_p,

+ const struct stat *st,

+ int walk_flags,

+ void *arg)

+{

+ struct do_set_args *args = arg;

+ acl_t old_acl = NULL, old_default_acl = NULL;

+ acl_t acl = NULL, default_acl = NULL;

+ acl_t *xacl, *old_xacl;

+ acl_entry_t ent;

+ cmd_t cmd;

+ int which_entry;

+ int skip = 0, error;

+ char *acl_text;

+ int acl_modified = 0, default_acl_modified = 0;

+ int acl_mask_provided = 0, default_acl_mask_provided = 0;

+

+ /* Execute the commands in seq (read ACLs on demand) */

+ error = seq_get_cmd(args->seq, SEQ_FIRST_CMD, &cmd);

+ if (error == 0)

+ return 0;

+

+ while (error == 1) {

+ mode_t perm = cmd->c_perm;

+

+ if (cmd->c_type == ACL_TYPE_ACCESS) {

+ xacl = &acl;

+ old_xacl = &old_acl;

+ acl_modified = 1;

+ if (cmd->c_tag == ACL_MASK)

+ acl_mask_provided = 1;

+ } else {

+ xacl = &default_acl;

+ old_xacl = &old_default_acl;

+ default_acl_modified = 1;

+ if (cmd->c_tag == ACL_MASK)

+ default_acl_mask_provided = 1;

+ }

+

+ RETRIEVE_ACL(cmd->c_type);

+

+ /* Check for `X', and replace with `x' as appropriate. */

+ if (perm & CMD_PERM_COND_EXECUTE) {

+ perm &= ~CMD_PERM_COND_EXECUTE;

+ if (S_ISDIR(st->st_mode) || has_execute_perms(*xacl))

+ perm |= CMD_PERM_EXECUTE;

+ }

+

+ switch(cmd->c_cmd) {

+ case CMD_ENTRY_REPLACE:

+ ent = find_entry(*xacl, cmd->c_tag, cmd->c_id);

+ if (!ent) {

+ if (acl_create_entry(xacl, &ent) != 0)

+ goto fail;

+ acl_set_tag_type(ent, cmd->c_tag);

+ if (cmd->c_id != ACL_UNDEFINED_ID)

+ acl_set_qualifier(ent,

+   &cmd->c_id);

+ }

+ set_perm(ent, perm);

+ break;

+

+ case CMD_REMOVE_ENTRY:

+ ent = find_entry(*xacl, cmd->c_tag, cmd->c_id);

+ if (ent)

+ acl_delete_entry(*xacl, ent);

+ else

+ /* ignore */;

+ break;

+

+ case CMD_REMOVE_EXTENDED_ACL:

+ remove_extended_entries(acl);

+ break;

+

+ case CMD_REMOVE_ACL:

+ acl_free(*xacl);

+ *xacl = acl_init(5);

+ if (!*xacl)

+ goto fail;

+ break;

+

+ default:

+ errno = EINVAL;

+ goto fail;

+ }

+

+ error = seq_get_cmd(args->seq, SEQ_NEXT_CMD, &cmd);

+ }

+

+ if (error < 0)

+ goto fail;

+

+ /* Try to fill in missing entries */

+ if (default_acl && acl_entries(default_acl) != 0) {

+ xacl = &acl;

+ old_xacl = &old_acl;

+

+ if (!find_entry(default_acl, ACL_USER_OBJ, ACL_UNDEFINED_ID)) {

+ if (!acl)

+ RETRIEVE_ACL(ACL_TYPE_ACCESS);

+ clone_entry(acl, ACL_USER_OBJ,

+             &default_acl, ACL_USER_OBJ);

+ }

+ if (!find_entry(default_acl, ACL_GROUP_OBJ, ACL_UNDEFINED_ID)) {

+ if (!acl)

+ RETRIEVE_ACL(ACL_TYPE_ACCESS);

+ clone_entry(acl, ACL_GROUP_OBJ,

+             &default_acl, ACL_GROUP_OBJ);

+ }

+ if (!find_entry(default_acl, ACL_OTHER, ACL_UNDEFINED_ID)) {

+ if (!acl)

+ RETRIEVE_ACL(ACL_TYPE_ACCESS);

+ clone_entry(acl, ACL_OTHER,

+             &default_acl, ACL_OTHER);

+ }

+ }

+

+ /* update mask entries and check if ACLs are valid */

+ if (acl && acl_modified) {

+ if (acl_equiv_mode(acl, NULL) != 0) {

+ if (!acl_mask_provided &&

+     !find_entry(acl, ACL_MASK, ACL_UNDEFINED_ID))

+ clone_entry(acl, ACL_GROUP_OBJ,

+             &acl, ACL_MASK);

+ if (opt_recalculate != -1 &&

+     (!acl_mask_provided || opt_recalculate == 1))

+ acl_calc_mask(&acl);

+ }

+

+ error = acl_check(acl, &which_entry);

+ if (error < 0)

+ goto fail;

+ if (error > 0) {

+ acl_text = acl_to_any_text(acl, NULL, ',', 0);

+ fprintf(stderr, _("%s: %s: Malformed access ACL "

+ "`%s': %s at entry %d\n"), progname, path_p,

+ acl_text, acl_error(error), which_entry+1);

+ acl_free(acl_text);

+ goto cleanup;

+ }

+ }

+

+ if (default_acl && acl_entries(default_acl) != 0 &&

+     default_acl_modified) {

+ if (acl_equiv_mode(default_acl, NULL) != 0) {

+ if (!default_acl_mask_provided &&

+     !find_entry(default_acl,ACL_MASK,ACL_UNDEFINED_ID))

+ clone_entry(default_acl, ACL_GROUP_OBJ,

+             &default_acl, ACL_MASK);

+ if (opt_recalculate != -1 &&

+     (!default_acl_mask_provided ||

+      opt_recalculate == 1))

+ acl_calc_mask(&default_acl);

+ }

+

+ error = acl_check(default_acl, &which_entry);

+ if (error < 0)

+ goto fail;

+ if (error > 0) {

+ acl_text = acl_to_any_text(default_acl, NULL, ',', 0);

+ fprintf(stderr, _("%s: %s: Malformed default ACL "

+                   "`%s': %s at entry %d\n"),

+ progname, path_p, acl_text,

+ acl_error(error), which_entry+1);

+ acl_free(acl_text);

+ goto cleanup;

+ }

+ }

+

+ /* Only directores can have default ACLs */

+ if (default_acl && !S_ISDIR(st->st_mode) && (walk_flags & WALK_TREE_RECURSIVE)) {

+ /* In recursive mode, ignore default ACLs for files */

+ acl_free(default_acl);

+ default_acl = NULL;

+ }

+ /* check which ACLs have changed */

+ if (acl && old_acl && acl_cmp(old_acl, acl) == 0) {

+ acl_free(acl);

+ acl = NULL;

+ }

+ if ((default_acl && old_default_acl &&

+     acl_cmp(old_default_acl, default_acl) == 0)) {

+ acl_free(default_acl);

+ default_acl = NULL;

+ }

+

+ /* update the file system */

+ if (opt_test) {

+ print_test(stdout, path_p, st,

+            acl, default_acl);

+ skip = 1;

+ goto cleanup;

+ }

+

+ /* provided that the rules are updated, skip the later procudure */

+ if (acl || default_acl) 

+ skip = 0;

+ else

+ skip = 1;

+

+cleanup:

+ if (acl)

+ acl_free(acl);

+ if (old_acl)

+ acl_free(old_acl);

+ if (default_acl)

+ acl_free(default_acl);

+ if (old_default_acl)

+ acl_free(old_default_acl);

+ return skip;

+

+fail:

+ goto cleanup;

+}

+

 int

 do_set(

  const char *path_p,

@@ -269,6 +487,8 @@ do_set(

  char *acl_text;

  int acl_modified = 0, default_acl_modified = 0;

  int acl_mask_provided = 0, default_acl_mask_provided = 0;

+ int fd = -1, ret = -1, tried = 1, locked = 0;

+ const int MAX_TRIED_TIMES = 3;

 

  if (walk_flags & WALK_TREE_FAILED) {

  fprintf(stderr, "%s: %s: %s\n", progname, path_p, strerror(errno));

@@ -285,10 +505,50 @@ do_set(

       !(walk_flags & (WALK_TREE_TOPLEVEL | WALK_TREE_LOGICAL))))

  return 0;

 

+ /* skip setting if the rule is redundant */

+ if (do_set_need_skip(path_p, st, walk_flags, arg) == 1)

+ return 0;

+

  /* Execute the commands in seq (read ACLs on demand) */

  error = seq_get_cmd(args->seq, SEQ_FIRST_CMD, &cmd);

  if (error == 0)

  return 0;

+

+ /* add lock to guarantee the atomic read-write process */

+ fd = open(path_p, O_RDONLY);

+ if (fd == -1) {

+ printf("setfacl: open %s failed\n", path_p);

+ return 1;

+ }

+ /* try MAX_TRIED_TIMES times */

+ while (ret < 0)

+ {

+ ret = flock(fd, LOCK_EX | LOCK_NB);

+ if (ret == -1)

+ {

+ if (errno == EWOULDBLOCK)

+ {

+ printf("setfacl: %s is locked, retry\n", path_p);

+ tried++;

+ usleep(10000);

+ if (tried > MAX_TRIED_TIMES)

+ {

+ printf("setfacl: %s is locked by others for too long\n", path_p);

+ /* once failed to require lock, then report error */

+ errors ++;

+ goto cleanup;

+ }

+ }

+ else

+ {

+ printf("setfacl: flock(%s) failed, errno = %d, reason is %s\n", path_p, errno, strerror(errno));

+ errors ++;

+ goto cleanup;

+ }

+ }

+ }

+ locked = 1;

+

  while (error == 1) {

  mode_t perm = cmd->c_perm;

 

@@ -511,6 +771,10 @@ do_set(

  error = 0;

 

 cleanup:

+ if(locked)

+ flock(fd, LOCK_UN);

+ if(fd != -1)

+ close(fd);

  if (acl)

  acl_free(acl);

  if (old_acl)

-- 

2.27.0





reply via email to

[Prev in Thread] Current Thread [Next in Thread]