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

lib: Move validation out of lcfs_node_add_child() #341

Merged
merged 1 commit into from
Sep 13, 2024

Conversation

cgwalters
Copy link
Contributor

Unfortunately at least ostree does:

node = lcfs_node_new()
lcfs_node_add_child(parent, node);
lcfs_node_set_mode(node, ...)
So we basically have a completely uninitialized node as part of the tree.

This failed our basic "validate the mode" logic.

We can't do any validation at all in lcfs_node_add_child() so move it to the first time we walk the tree as part of lcfs_node_write_to().

Also as part of fixing this: Today we don't really have coverage of the C library as it may be used by external consumers. Add a unit test to verify this, and we can build on this more.

@cgwalters
Copy link
Contributor Author

Ah yeah of course since this is C a ~5 line unit test has a double-unref, thankfully caught by ASAN.

Copy link
Collaborator

@allisonkarlitskaya allisonkarlitskaya left a comment

Choose a reason for hiding this comment

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

The patch looks fine as it is but it's sort of clear that the API use by ostree is not what was intended here...

Would it be super disruptive to assert that it's invalid to make changes to the node after adding it to the parent? I guess that would more or less be an ABI break and need an soname bump, right? I'm thinking about the recent _rdev64() changes... What's our guarantee here?

@cgwalters
Copy link
Contributor Author

Yeah, agree. Here's one thing I've been thinking about as followups to all this:

  • Add lcfs_node_new_symlink() lcfs_node_new_inline_file() lcfs_node_new_dev() etc that take their required properties as constructors (very much like how things are modeled on the Rust side)
  • These APIs also set a node->strict = true property that enforces more validation
  • When `lcfs_node_add_child() is invoked on a node with strict = true, any further mutation APIs return an error?

Actually we could just add lcfs_node_new_dev() now and not ship lcfs_node_set_rdev64 I guess.

guess that would more or less be an ABI break and need an soname bump, right?

Right, I don't want to scope that into this at this time. It would be painful.

@allisonkarlitskaya
Copy link
Collaborator

Add lcfs_node_new_symlink() lcfs_node_new_inline_file() lcfs_node_new_dev() etc that take their required properties as constructors (very much like how things are modeled on the Rust side)

This sounds nice.

Is your plan to land this PR, add those APIs, then back out the rdev64 setter, then deprecate the existing _new() API as a way to more or less remove the ability to create ->strict == false nodes? And then port ostree over? Because it's sort of at cross purposes to this PR to move validation to 'later' because of node mutability if we intend to head in a direction where the nodes are no longer mutable...

@cgwalters
Copy link
Contributor Author

My hope was to land this PR basically as is alongside #342 and get a new release out and leave other things to followup.

@cgwalters
Copy link
Contributor Author

Adding a new API is going to need more tests, and we'd need to rework the fuzzing entrypoint to support both the old and new API, which is quite doable but also not trivial, so I'd rather get fixes out for what we have now as a release.

@@ -390,12 +390,6 @@ int lcfs_write_to(struct lcfs_node_s *root, struct lcfs_write_options_s *options
struct lcfs_ctx_s *ctx;
int res;

// Verify the root; because lcfs_node_add_child also verifies children,
// we should have sanity checked all nodes.
if (lcfs_node_last_ditch_validation(root) < 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

One very minor nag: we handle validation of the node from the main writer code currently but your patch moves it to the erofs implementation code. Of course, there are no other implementations... but it seems a bit odd.

I guess the implementation is the thing iterating the nodes in the end, though, so there's not really much of another way...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Of course, there are no other implementations... but it seems a bit odd.

Yeah the current code structure is AIUI a legacy of the time when there were two composefs implementations - one kernel native, one using the architecture today.

It probably makes sense to fold some of lcfs-writer.c and lcfs-writer-erofs.c - but I wouldn't say all?

Anyways yeah, AFAICS the place I moved the validation is the only place we can do it right now.

tests/test-lcfs.c Show resolved Hide resolved
Unfortunately at least ostree does:

node = lcfs_node_new()
lcfs_node_add_child(parent, node);
lcfs_node_set_mode(node, ...)
So we basically have a completely uninitialized node
as part of the tree.

This failed our basic "validate the mode" logic.

We can't do any validation at all in `lcfs_node_add_child()` so
move it to the first time we walk the tree as part of
`lcfs_node_write_to()`.

Also as part of fixing this: Today we don't really have coverage
of the C library as it may be used by external consumers.
Add a unit test to verify this, and we can build on this more.

Signed-off-by: Colin Walters <walters@verbum.org>
Copy link
Collaborator

@allisonkarlitskaya allisonkarlitskaya left a comment

Choose a reason for hiding this comment

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

Thanks for the test! This looks good to me.

{
char *bufp = NULL;
size_t bufsz = 0;
FILE *buf = open_memstream(&bufp, &bufsz);
Copy link
Collaborator

Choose a reason for hiding this comment

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

The memstream was more than I was expecting!

options.file_write_cb = write_cb;

int r = lcfs_write_to(node, &options);
int saved_errno = errno;
Copy link
Collaborator

Choose a reason for hiding this comment

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

It took me a real long time to convince myself that this was right, but indeed we set errno = EINVAL in the mode-validation code. Is there some story behind the various approaches to error handling in libcomposefs? The mount code seems to prefer returning negative errnos, at least internally...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The mount code seems to prefer returning negative errnos, at least internally...

It's quite possible that I or someone else messed up the sign at some point 😢

Copy link
Collaborator

@allisonkarlitskaya allisonkarlitskaya Sep 13, 2024

Choose a reason for hiding this comment

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

I mean that instead of

   errno = EINVAL;
   return -1;

you get kernel/systemd-style

   return -EINVAL;

I don't think anyone has ever returned positive errno values.... as far as I know :)

@cgwalters cgwalters merged commit b6f58f2 into containers:main Sep 13, 2024
13 checks passed
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.

2 participants