Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src/libcrun: fix handling of device paths with trailing slashes #1517

Merged
merged 1 commit into from
Sep 4, 2024

Conversation

sohankunkerkar
Copy link
Member

This change handles device paths with trailing slashes correctly. It address the following issue when we use crun as the default container runtime:

Error: container create failed: mknod `/mnt/`: No such file or directory .

@haircommander
Copy link
Contributor

FYI this came up because of kubernetes/kubernetes#126641

@haircommander
Copy link
Contributor

I think SecureJoin is why this works for runc

src/libcrun/linux.c Outdated Show resolved Hide resolved
@sohankunkerkar sohankunkerkar force-pushed the fix-trailing-slash branch 2 times, most recently from ddc5c33 to ede6e99 Compare August 12, 2024 18:20
Copy link

podman system tests failed. @containers/packit-build please check.

Comment on lines 1619 to 1624
if (normalized_path[path_len - 1] == '/')
{
normalized_path[path_len - 1] = '\0';
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work if there are two slashes at the end (which is otherwise OK).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically need to replace if with while.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having said that, I'd rather fix it in e.g. cri-o. Asking to create a block device with a slash at the end is somewhat weird.

OTOH runc for some reason works.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree CRI-O is handing crun garbage. I am fine with fixing this, but the real source of the problem is a device with a trailing "/" is and invalid device.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OTOH runc for some reason works.

#1517 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@haircommander, should we try to handle this in CRI-O before it even gets to a runtime?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could for sure, the code would be easier to write in go

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mind handling this in CRI-O. The only caveat is that if some other CRI implementation hits the same issue, then they might question the crun code because this case is already handled in runc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a "cross the bridge when we get there" scenario. This was opened up because it's a bug in the test and it's arguable whether runc should even silently allow it (it's undefined in the runtime spec).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I would prefer, personally, that the user receive feedback earlier that their path is not conformant (per what @rhatdan said above) rather than us correcting it behind the scenes, so to speak.

If not, then we need to clean the paths up in runc and crun, too. Perhaps even in CRI-O as the first line of defence against malformed values.

src/libcrun/linux.c Outdated Show resolved Hide resolved
src/libcrun/linux.c Outdated Show resolved Hide resolved
src/libcrun/linux.c Outdated Show resolved Hide resolved
src/libcrun/linux.c Outdated Show resolved Hide resolved
src/libcrun/linux.c Outdated Show resolved Hide resolved
@sohankunkerkar sohankunkerkar force-pushed the fix-trailing-slash branch 2 times, most recently from 33a9e7a to 0a75232 Compare August 13, 2024 12:09
src/libcrun/utils.c Outdated Show resolved Hide resolved
const char *rel_dev = relative_path_under_dev (device->path);
// Normalize path by removing trailing slash if it exists.
cleanup_free char *normalized_path = xstrdup (fullname);
cleanup_free char *last_slash = trim_trailing_slashes (normalized_path);
Copy link
Member

@kwilczynski kwilczynski Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consume_trailing_slashes(normalized_path);

See my other comment below.

Comment on lines 1688 to 1697
cleanup_free char *buffer = NULL;
char *dirname = normalized_path, *basename;
cleanup_close int dirfd = -1;
char *basename, *tmp;

buffer = xstrdup (device->path);
dirname = buffer;

tmp = strrchr (buffer, '/');
*tmp = '\0';
basename = tmp + 1;
// Find the last slash in the normalized path after trimming
char *last_slash = strrchr (normalized_path, '/');
if (last_slash)
basename = last_slash + 1;
else
basename = normalized_path;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous code looks for the directory name and base directory. We can update it a bit, given the use of normalized_path, or keep it at large as-is per the current version.

cleanup_close int dirfd = -1;
cleanup_free char dirname = NULL;
char *basename, *found;

dirname = strdup(normalized_path);

found = strrchr(dirname, '/');
*found = '\0';

basename = found + 1;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I think the previous implementation will still work, but the dirname points to the same memory as normalized_path and any modifications to dirname would also affect normalized_path, which can lead to unintended result if not handled properly. I believe we need a slight modification the code you suggested. Thanks for the pointers.

          cleanup_close int dirfd = -1;
          cleanup_free char *dirname = NULL;
          char *basename, *found;

          dirname = strdup (normalized_path);

          found = strrchr (dirname, '/');
          if (found)
              *found = '\0';

          basename = found + 1;

Copy link
Member

@kwilczynski kwilczynski Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sohankunkerkar, we might have to add a special case, if possible, where the path would be simply a single forward slash (so "/") or any value that is not a path, like "test" (not sure if ever possible to get such a value), then the code would misbehave.

Problematic values: "" (empty string; strlen() == 0), "/", "test", "test/"; at the moment.

@@ -422,6 +422,8 @@ get_process_exit_status (int status)
uid_t get_overflow_uid (void);
gid_t get_overflow_gid (void);

void consume_trailing_slashes (char *path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

src/libcrun/linux.c Outdated Show resolved Hide resolved
Copy link

Ephemeral COPR build failed. @containers/packit-build please check.

if (found)
*found = '\0';

basename = found + 1;
Copy link
Member

@kwilczynski kwilczynski Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is something interesting about this implementation, generally.

The added NULL check above is fine. However, if we ever get a NULL in found, then we would have a value of 1 in basename, and then the attempt to read from it will be either SIGSEGV, hopefully, or some undefined behaviour (worst case; should not happen on Linux, hopefully).

The old (original) code also has this problem.

Comment on lines 2519 to 2523
// Handle the case where the path is just a single slash.
if (strcmp (path, "/") == 0)
{
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably does not belong here, as it is a utility function.

Why? The handling of a single slash is a use case specific to the code that calls this function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I don't think we need to handle this condition separately. It will covered as part of the below snippet:

  cleanup_free char *normalized_path = xstrdup (fullname);
  consume_trailing_slashes (normalized_path);
  if (normalized_path[0] == '\0')
    strcpy (normalized_path, "/");

Comment on lines 1696 to 1705
found = strrchr (dirname, '/');
if (found)
{
*found = '\0';
basename = found + 1;
}
else
{
basename = dirname;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be the correct behaviour here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that if there are no slashes in the path, basename is set to the entire path. This prevents any uninitialized variables and potential segmentation faults. Just an improvement over the original code. We need to clarify with @kolyshkin or @giuseppe to understand if there's any repercussion to it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example Scenarios:

  • Path with Slashes:
    Input: "/path/to/file.txt"
    Operation:
    found = strrchr(dirname, '/') will point to the last slash ('/').
    *found = '\0' will truncate dirname to "/path/to".
    basename = found + 1 will set basename to "file.txt" (the part after the last slash).
    Result:
    dirname = "/path/to"
    basename = "file.txt"

  • Path Without Slashes:
    Input: "file.txt"
    Operation:
    found = strrchr(dirname, '/') will return NULL since there are no slashes in the path.
    The line basename = found ? found + 1 : dirname; will evaluate to basename = dirname;, meaning basename will be set to "file.txt".
    Result:
    dirname = "file.txt"
    basename = "file.txt"

  • Single Slash:
    Input: "/"
    Operation:
    found = strrchr(dirname, '/') will point to the single slash.
    *found = '\0' will truncate dirname to an empty string ("").
    basename = found ? found + 1 : dirname; will evaluate to basename = ""; (because found + 1 points to the character after the null terminator).
    Result:
    dirname = ""
    basename = ""

  • Empty String:
    Input: ""
    Operation:
    found = strrchr(dirname, '/') will return NULL.
    basename = found ? found + 1 : dirname; will evaluate to basename = ""; (because dirname is empty).
    Result:
    dirname = ""
    basename = ""

Comment on lines 2534 to 2537
if (path[0] == '\0')
{
strcpy (path, "/");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. See my above comment.

@sohankunkerkar sohankunkerkar changed the title [WIP] src/libcrun: fix handling of device paths with trailing slashes src/libcrun: fix handling of device paths with trailing slashes Aug 15, 2024
src/libcrun/utils.c Outdated Show resolved Hide resolved
src/libcrun/utils.c Outdated Show resolved Hide resolved
Copy link
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a minor comment, otherwise LGTM

src/libcrun/linux.c Show resolved Hide resolved
// Normalize the path by removing trailing slashes.
cleanup_free char *normalized_path = xstrdup (fullname);
consume_trailing_slashes (normalized_path);
if (normalized_path[0] == '\0')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (normalized_path[0] == '\0')
if (is_empty_string(normalized_path))

This can also be used to check if the fullname variable is empty, per @giuseppe's request.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sohankunkerkar, not using is_empty_string() here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use is_empty_string() here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is_empty_string would be redundant in this case as consume_trailing_slashes already handles the null and empty string cases, checking normalized_path[0] = '\0' immediately afterward is sufficient. Let me know if I'm missing out any test case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The helper would be fine. I think we should use it. Also, it improves readability a bit (since the intent will be obvious given the function name).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of a shame we didn't use the helper here. Consistency would be nice.

Comment on lines 1687 to 1704
char *dirname;
cleanup_free char *buffer = NULL;
cleanup_close int dirfd = -1;
char *basename, *tmp;
cleanup_free char *dirname = NULL;
char *basename, *found;

buffer = xstrdup (device->path);
dirname = buffer;
dirname = strdup (normalized_path);

tmp = strrchr (buffer, '/');
*tmp = '\0';
basename = tmp + 1;
found = strrchr (dirname, '/');
if (found)
*found = '\0';

basename = found ? found + 1 : dirname;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@giuseppe, are you OK with this refactoring here? Especially how the basename will be set to dirname. I want to make sure we won't break anything.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that seems OK to me

Copy link

podman system tests failed. @containers/packit-build please check.


buffer = xstrdup (device->path);
dirname = buffer;
dirname = strdup (normalized_path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strdup can return NULL. Let's use xstrdup

Comment on lines 1687 to 1704
char *dirname;
cleanup_free char *buffer = NULL;
cleanup_close int dirfd = -1;
char *basename, *tmp;
cleanup_free char *dirname = NULL;
char *basename, *found;

buffer = xstrdup (device->path);
dirname = buffer;
dirname = strdup (normalized_path);

tmp = strrchr (buffer, '/');
*tmp = '\0';
basename = tmp + 1;
found = strrchr (dirname, '/');
if (found)
*found = '\0';

basename = found ? found + 1 : dirname;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that seems OK to me

Copy link

Ephemeral COPR build failed. @containers/packit-build please check.

Copy link

podman system tests failed. @containers/packit-build please check.

@@ -1611,7 +1611,14 @@ libcrun_create_dev (libcrun_container_t *container, int devfd, int srcfd,
int rootfsfd = get_private_data (container)->rootfsfd;
const char *rootfs = get_private_data (container)->rootfs;
size_t rootfs_len = get_private_data (container)->rootfs_len;
const char *rel_dev = relative_path_under_dev (device->path);
if (is_empty_string (fullname))
return crun_make_error (err, EINVAL, "fullname is empty");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

users don't know what fullname is. Let's clarify the error message

This change handles device paths with trailing slashes correctly.

Signed-off-by: Sohan Kunkerkar <sohank2602@gmail.com>
Copy link

Ephemeral COPR build failed. @containers/packit-build please check.

Copy link

podman system tests failed. @containers/packit-build please check.

Copy link
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@giuseppe giuseppe merged commit 841a0de into containers:main Sep 4, 2024
33 of 56 checks passed
@sohankunkerkar sohankunkerkar deleted the fix-trailing-slash branch September 4, 2024 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants