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

Potential null pointer fixes / checks #4132

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

OverOrion
Copy link
Collaborator

In #4123 the fix was to check the return value of a failable function.
With the help of Infer I was able to find similar cases and fix them.

@github-actions
Copy link
Contributor

No news file has been detected. Please write one, if applicable.

@kira-syslogng
Copy link
Contributor

Build FAILURE

@@ -389,7 +389,7 @@ log_reader_work_finished(void *s)
self->notify_code = 0;
log_pipe_notify(self->control, notify_code, self);
}
if (self->super.super.flags & PIF_INITIALIZED)
if ((self->super.super.flags & PIF_INITIALIZED) && self->proto)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this should rather be an assert in the body of the if.

self->proto should never be a NULL in this case (we had a bug in this path a while back in case of threaded(no)), and I reviewed that we have it covered. The bug at that time was caused by use-after-free.

So, I would rather get an abort in case self->proto is NULL here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We concluded that self->proto being NULL here is allowed, as a pending_close can NULL it out a few lines above.

Copy link
Collaborator Author

@OverOrion OverOrion May 7, 2023

Choose a reason for hiding this comment

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

This was a rather naive approach, I blindly copied what I had found in LogWriter, thanks for catching it!

I do remember now, yes, self->proto can be NULL here.

@@ -91,6 +91,7 @@ control_command_thread_run(ControlCommandThread *self)
self->thread_finished.cookie = control_command_thread_ref(self);
iv_event_register(&self->thread_finished);

g_assert(self->connection);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't see how this could occur. self->connection is initialized in the constructor by taking a ref from a constructor argument. The only call-site that calls control_command_thread_new() is control-connection which passes "self" to that argument.

I wouldn't want to spray unneeded asserts everywhere. If we want to add this anywhere, it should be in the constructor I think, but I don't think there's too much pressure doing it even there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Indeed, it was a false positive, thanks once again for the catch!

lib/logwriter.c Outdated
@@ -1201,7 +1199,7 @@ log_writer_write_message(LogWriter *self, LogMessage *msg, LogPathOptions *path_
{
if (!consumed)
{
g_free(self->line_buffer->str);
g_string_free(self->line_buffer, TRUE);
log_writer_realloc_line_buffer(self);
Copy link
Collaborator

Choose a reason for hiding this comment

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

this doubles the number of allocations as it will allocate both the GString struct and its "str" member. The aim was to optimize away this case.

A better alternative would be to use scratch-buffers for this purpose, or maybe wrap this ugly trick into a nicer looking function (aka g_string_steal).

Also, this seems to be a use-after-free, you are freeing self->line_buffer and then calling g_string_set_size() on it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I gave it a second thought and refactored the function to take a ownership_transferred boolean which decides whether the old buffer should be freed or not. I think it helps with readability as the freeing would happen inside the function instead of the call side.

I am more than happy to revert it, because it does not contribute much to the spirit of the PR either.

@@ -479,7 +479,6 @@ affile_dd_reuse_writer(gpointer key, gpointer value, gpointer user_data)
affile_dw_set_owner(writer, self);
if (!log_pipe_init(&writer->super))
{
affile_dw_set_owner(writer, NULL);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think removing this would cause a leak through the circular reference through the AFFileDestWriter->owner member.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree, affile_dw_set_owner() is necessary here, but its implementation needs to be fixed in order not to dereference NULL pointers. This is a real issue, we've reproduced the crash.

affile_dw_unset_owner() would be even cleaner.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks once again, affile_dw_set_owner got fixed, it should take care of the possible NULL owner now.

Copy link
Collaborator

@bazsi bazsi left a comment

Choose a reason for hiding this comment

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

I've filed a few comments that should change before this can be merged.

@MrAnno
Copy link
Collaborator

MrAnno commented Oct 1, 2022

@OverOrion Do you have plans to finish this PR?

@MrAnno MrAnno changed the title Potential null pointer fixes / checks WIP: Potential null pointer fixes / checks Dec 29, 2022
@MrAnno MrAnno marked this pull request as draft December 29, 2022 13:00
Fix a potential null pointer dereference

Signed-off-by: Szilárd Parrag <szilard.parrag@gmail.com>
Signed-off-by: Szilárd Parrag <szilard.parrag@gmail.com>
Refactored this function to have it decied whether the old buffer should
be freed or not.

Signed-off-by: Szilárd Parrag <szilard.parrag@gmail.com>
@OverOrion OverOrion force-pushed the potential_null_ptr_fixes branch 2 times, most recently from bc13c3a to 834fd74 Compare May 7, 2023 12:29
@MrAnno MrAnno self-requested a review May 7, 2023 12:35
@MrAnno
Copy link
Collaborator

MrAnno commented May 7, 2023

Thanks for fixing these. I've reproduced one of the crashes, will review the PR tomorrow.

@OverOrion OverOrion marked this pull request as ready for review May 7, 2023 12:50
@OverOrion OverOrion changed the title WIP: Potential null pointer fixes / checks Potential null pointer fixes / checks May 7, 2023
@@ -1203,17 +1206,15 @@ log_writer_write_message(LogWriter *self, LogMessage *msg, LogPathOptions *path_

self->partial_write = (status == LPS_PARTIAL);

if (consumed)
log_writer_realloc_line_buffer(self);
log_writer_realloc_line_buffer(self, consumed);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This does not look like an equivalent transformation to me.
What's the idea behind this patch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The idea was that the freeing was outside the other buffer manipulations and I wanted them to be close to each other (on the call site -> inside the function).

The consumed boolean is responsible for deciding whether a specific buffer has been passed yet or not. If it has been passed, then it should not be freed, because the reference is needed (there is something that refers to it).
If it has not been passed (nothing else refers to it), then the old buffer must be freed.

Basically I moved the g_free call into the function body.

@@ -292,24 +292,28 @@ static void
affile_dw_set_owner(AFFileDestWriter *self, AFFileDestDriver *owner)
{
GlobalConfig *cfg = log_pipe_get_config(&owner->super.super.super);
log_pipe_set_config(&self->super, cfg);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This does not fix the NULL pointer deref issue as log_pipe_get_config dereferences owner.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are right, I messed it up during the rebase 🤦 .

{
if (self->owner)
log_pipe_unref(&self->owner->super.super.super);
self->owner = NULL;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably we should do something with self->super.expr_node too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Upon a closer look it seems that the expr_node setting is done without freeing the old one in other places as well.

Should I fix it in this PR or in a separate one?

In very rare (and unrealistic) cases syslog-ng could crash if it was
reloaded and it was unable to reuse it's file writers.

Signed-off-by: Szilárd Parrag <szilard.parrag@gmail.com>
Signed-off-by: Szilard Parrag <szilard.parrag@gmail.com>
Signed-off-by: Szilárd Parrag <szilard.parrag@gmail.com>
@OverOrion OverOrion requested a review from MrAnno May 25, 2023 07:31
@MrAnno MrAnno removed their request for review May 22, 2024 12:59
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.

None yet

4 participants