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
base: master
Are you sure you want to change the base?
Conversation
No news file has been detected. Please write one, if applicable. |
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
lib/control/control-command-thread.c
Outdated
@@ -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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
@OverOrion Do you have plans to finish this PR? |
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>
bc13c3a
to
834fd74
Compare
Thanks for fixing these. I've reproduced one of the crashes, will review the PR tomorrow. |
834fd74
to
62f5e81
Compare
2f77d1f
to
1a139a1
Compare
@@ -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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
modules/affile/affile-dest.c
Outdated
@@ -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); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>
1a139a1
to
1107829
Compare
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.