diff mbox series

libsemanage: Fallback to semanage_copy_dir when rename() failed

Message ID 20220323173412.1538778-1-plautrba@redhat.com (mailing list archive)
State Superseded
Headers show
Series libsemanage: Fallback to semanage_copy_dir when rename() failed | expand

Commit Message

Petr Lautrbach March 23, 2022, 5:34 p.m. UTC
In some circumstances, like semanage-store being on overlayfs, rename()
could fail with EXDEV - Invalid cross-device link. This is due to fact
that overlays doesn't support rename() is source and target are not on
the same layer, e.g. in rpm-ostree based containers. Even though it's
not atomic operation, it's better to try to to copy files from src to
dst on our own in this case. Next rebuild will probably not fail as the
new directories will be on the same layer.

Fixes: https://github.com/SELinuxProject/selinux/issues/343
Before:
    # semodule -B
    libsemanage.semanage_commit_sandbox: Error while renaming /etc/selinux/targeted/active to /etc/selinux/targeted/previous. (Invalid cross-device link).
semodule:  Failed!

After:
    # semodule -B
    Warning: rename(/etc/selinux/targeted/active, /etc/selinux/targeted/previous) failed: Invalid cross-device link, fallback to non-atomic semanage_copy_dir_flags()

Reported-by: Joseph Marrero Corchado <jmarrero@redhat.com>
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
---
 libsemanage/src/semanage_store.c | 31 ++++++++++++++++++++++++-------
 1 file changed, 24 insertions(+), 7 deletions(-)

Comments

Ondrej Mosnacek March 23, 2022, 6:11 p.m. UTC | #1
On Wed, Mar 23, 2022 at 6:34 PM Petr Lautrbach <plautrba@redhat.com> wrote:

In subject: s/Fallback/Fall back/ and s/failed/fails/

Other than a couple nits above and below the patch looks good to me.

I think we could make the fallback rename atomic by creating a file
before the rename, removing it afterwards, and checking if it exists
at the beginning of each transaction (in case it does, we would retry
the rename from scratch before proceeding). But maybe it's not worth
the hassle for such a corner case...

> In some circumstances, like semanage-store being on overlayfs, rename()
> could fail with EXDEV - Invalid cross-device link. This is due to fact

due to the

> that overlays doesn't support rename() is source and target are not on

s/is/if/

> the same layer, e.g. in rpm-ostree based containers. Even though it's
> not atomic operation, it's better to try to to copy files from src to

s/to to/to/

> dst on our own in this case. Next rebuild will probably not fail as the
> new directories will be on the same layer.
>
> Fixes: https://github.com/SELinuxProject/selinux/issues/343
> Before:
>     # semodule -B
>     libsemanage.semanage_commit_sandbox: Error while renaming /etc/selinux/targeted/active to /etc/selinux/targeted/previous. (Invalid cross-device link).
> semodule:  Failed!
>
> After:
>     # semodule -B
>     Warning: rename(/etc/selinux/targeted/active, /etc/selinux/targeted/previous) failed: Invalid cross-device link, fallback to non-atomic semanage_copy_dir_flags()
>
> Reported-by: Joseph Marrero Corchado <jmarrero@redhat.com>
> Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
> ---
>  libsemanage/src/semanage_store.c | 31 ++++++++++++++++++++++++-------
>  1 file changed, 24 insertions(+), 7 deletions(-)
>
> diff --git a/libsemanage/src/semanage_store.c b/libsemanage/src/semanage_store.c
> index 767f05cb2853..aa44ebb7e481 100644
> --- a/libsemanage/src/semanage_store.c
> +++ b/libsemanage/src/semanage_store.c
> @@ -697,6 +697,10 @@ int semanage_store_access_check(void)
>
>  /********************* other I/O functions *********************/
>
> +static int semanage_rename(const char *tmp, const char *dst);
> +int semanage_remove_directory(const char *path);rename
> +static int semanage_copy_dir_flags(const char *src, const char *dst, int flag);
> +
>  /* Callback used by scandir() to select files. */
>  static int semanage_filename_select(const struct dirent *d)
>  {
> @@ -760,7 +764,7 @@ int semanage_copy_file(const char *src, const char *dst, mode_t mode,
>                 retval = -1;
>         }
>
> -       if (!retval && rename(tmp, dst) == -1)
> +       if (!retval && semanage_rename(tmp, dst) == -1)
>                 return -1;
>
>  out:
> @@ -768,7 +772,20 @@ out:
>         return retval;
>  }
>
> -static int semanage_copy_dir_flags(const char *src, const char *dst, int flag);
> +static int semanage_rename(const char *src, const char *dst) {
> +       int retval = -1;

The value is immediately overwritten, so there is no need to
initialize retval to -1 here.

> +
> +       retval = rename(src, dst);
> +       if (retval == 0 || errno != EXDEV)
> +               return retval;
> +
> +       /* we can't use rename() due to filesystem limitation, lets try to copy files manually */
> +       fprintf(stderr, "Warning: rename(%s, %s) failed: %s, fallback to non-atomic semanage_copy_dir_flags()\n", src, dst, strerror(errno));

I believe this should use the WARN() macro from debug.h instead. Also
s/fallback/fall back/.

> +       if (semanage_copy_dir_flags(src, dst, 1) == -1) {
> +               return -1;
> +       }
> +       return semanage_remove_directory(src);
> +}
>
>  /* Copies all of the files from src to dst, recursing into
>   * subdirectories.  Returns 0 on success, -1 on error. */
> @@ -1770,7 +1787,7 @@ static int semanage_commit_sandbox(semanage_handle_t * sh)
>                 goto cleanup;
>         }
>
> -       if (rename(active, backup) == -1) {
> +       if (semanage_rename(active, backup) == -1) {
>                 ERR(sh, "Error while renaming %s to %s.", active, backup);
>                 retval = -1;
>                 goto cleanup;
> @@ -1779,12 +1796,12 @@ static int semanage_commit_sandbox(semanage_handle_t * sh)
>         /* clean up some files from the sandbox before install */
>         /* remove homedir_template from sandbox */
>
> -       if (rename(sandbox, active) == -1) {
> +       if (semanage_rename(sandbox, active) == -1) {
>                 ERR(sh, "Error while renaming %s to %s.", sandbox, active);
>                 /* note that if an error occurs during the next
>                  * function then the store will be left in an
>                  * inconsistent state */
> -               if (rename(backup, active) < 0)
> +               if (semanage_rename(backup, active) < 0)
>                         ERR(sh, "Error while renaming %s back to %s.", backup,
>                             active);
>                 retval = -1;
> @@ -1795,10 +1812,10 @@ static int semanage_commit_sandbox(semanage_handle_t * sh)
>                  * function then the store will be left in an
>                  * inconsistent state */
>                 int errsv = errno;
> -               if (rename(active, sandbox) < 0)
> +               if (semanage_rename(active, sandbox) < 0)
>                         ERR(sh, "Error while renaming %s back to %s.", active,
>                             sandbox);
> -               else if (rename(backup, active) < 0)
> +               else if (semanage_rename(backup, active) < 0)
>                         ERR(sh, "Error while renaming %s back to %s.", backup,
>                             active);
>                 else
> --
> 2.35.1
>
diff mbox series

Patch

diff --git a/libsemanage/src/semanage_store.c b/libsemanage/src/semanage_store.c
index 767f05cb2853..aa44ebb7e481 100644
--- a/libsemanage/src/semanage_store.c
+++ b/libsemanage/src/semanage_store.c
@@ -697,6 +697,10 @@  int semanage_store_access_check(void)
 
 /********************* other I/O functions *********************/
 
+static int semanage_rename(const char *tmp, const char *dst);
+int semanage_remove_directory(const char *path);
+static int semanage_copy_dir_flags(const char *src, const char *dst, int flag);
+
 /* Callback used by scandir() to select files. */
 static int semanage_filename_select(const struct dirent *d)
 {
@@ -760,7 +764,7 @@  int semanage_copy_file(const char *src, const char *dst, mode_t mode,
 		retval = -1;
 	}
 
-	if (!retval && rename(tmp, dst) == -1)
+	if (!retval && semanage_rename(tmp, dst) == -1)
 		return -1;
 
 out:
@@ -768,7 +772,20 @@  out:
 	return retval;
 }
 
-static int semanage_copy_dir_flags(const char *src, const char *dst, int flag);
+static int semanage_rename(const char *src, const char *dst) {
+	int retval = -1;
+
+	retval = rename(src, dst);
+	if (retval == 0 || errno != EXDEV)
+		return retval;
+
+	/* we can't use rename() due to filesystem limitation, lets try to copy files manually */
+	fprintf(stderr, "Warning: rename(%s, %s) failed: %s, fallback to non-atomic semanage_copy_dir_flags()\n", src, dst, strerror(errno));
+	if (semanage_copy_dir_flags(src, dst, 1) == -1) {
+		return -1;
+	}
+	return semanage_remove_directory(src);
+}
 
 /* Copies all of the files from src to dst, recursing into
  * subdirectories.  Returns 0 on success, -1 on error. */
@@ -1770,7 +1787,7 @@  static int semanage_commit_sandbox(semanage_handle_t * sh)
 		goto cleanup;
 	}
 
-	if (rename(active, backup) == -1) {
+	if (semanage_rename(active, backup) == -1) {
 		ERR(sh, "Error while renaming %s to %s.", active, backup);
 		retval = -1;
 		goto cleanup;
@@ -1779,12 +1796,12 @@  static int semanage_commit_sandbox(semanage_handle_t * sh)
 	/* clean up some files from the sandbox before install */
 	/* remove homedir_template from sandbox */
 
-	if (rename(sandbox, active) == -1) {
+	if (semanage_rename(sandbox, active) == -1) {
 		ERR(sh, "Error while renaming %s to %s.", sandbox, active);
 		/* note that if an error occurs during the next
 		 * function then the store will be left in an
 		 * inconsistent state */
-		if (rename(backup, active) < 0)
+		if (semanage_rename(backup, active) < 0)
 			ERR(sh, "Error while renaming %s back to %s.", backup,
 			    active);
 		retval = -1;
@@ -1795,10 +1812,10 @@  static int semanage_commit_sandbox(semanage_handle_t * sh)
 		 * function then the store will be left in an
 		 * inconsistent state */
 		int errsv = errno;
-		if (rename(active, sandbox) < 0)
+		if (semanage_rename(active, sandbox) < 0)
 			ERR(sh, "Error while renaming %s back to %s.", active,
 			    sandbox);
-		else if (rename(backup, active) < 0)
+		else if (semanage_rename(backup, active) < 0)
 			ERR(sh, "Error while renaming %s back to %s.", backup,
 			    active);
 		else