Message ID | 1535644031-848-8-git-send-email-Liam.Merwick@oracle.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | off-by-one and NULL pointer accesses detected by static analysis | expand |
On 08/30/2018 10:47 AM, Liam Merwick wrote: > Incorrect checking of flags could result in uninitialized > file descriptor being used. > > Signed-off-by: Liam Merwick <Liam.Merwick@oracle.com> > Reviewed-by: Darren Kenny <Darren.Kenny@oracle.com> > Reviewed-by: Mark Kanda <Mark.Kanda@oracle.com> > --- > io/channel-command.c | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > > diff --git a/io/channel-command.c b/io/channel-command.c > index 3e7eb17eff54..38deb687da21 100644 > --- a/io/channel-command.c > +++ b/io/channel-command.c > @@ -59,10 +59,10 @@ qio_channel_command_new_spawn(const char *const argv[], > > flags = flags & O_ACCMODE; > > - if (flags == O_RDONLY) { > + if ((flags & O_RDONLY) == O_RDONLY) { NACK. O_RDONLY and O_WRONLY are subsets of O_ACCMODE, which we already masked out above. On some systems, we have: O_RDONLY == 0 O_WRONLY == 1 O_RDWR == 2 On other systems, we have: O_RDONLY == 1 O_WRONLY == 2 O_RDWR == 3 Either way, if the user passed in O_RDWR, (flags & O_RDONLY) == O_RDONLY returns true, which is wrong. O_ACCMODE was historically 0x3, although now that POSIX has O_EXEC and O_SEARCH (which can be the same bit pattern), some systems now make O_ACCMODE occupy 3 bits instead of 2.
On 30/08/2018 17:18, Eric Blake wrote: > On 08/30/2018 10:47 AM, Liam Merwick wrote: >> Incorrect checking of flags could result in uninitialized >> file descriptor being used. >> >> Signed-off-by: Liam Merwick <Liam.Merwick@oracle.com> >> Reviewed-by: Darren Kenny <Darren.Kenny@oracle.com> >> Reviewed-by: Mark Kanda <Mark.Kanda@oracle.com> >> --- >> io/channel-command.c | 4 ++-- >> 1 file changed, 2 insertions(+), 2 deletions(-) >> >> diff --git a/io/channel-command.c b/io/channel-command.c >> index 3e7eb17eff54..38deb687da21 100644 >> --- a/io/channel-command.c >> +++ b/io/channel-command.c >> @@ -59,10 +59,10 @@ qio_channel_command_new_spawn(const char *const >> argv[], >> flags = flags & O_ACCMODE; >> - if (flags == O_RDONLY) { >> + if ((flags & O_RDONLY) == O_RDONLY) { > > NACK. O_RDONLY and O_WRONLY are subsets of O_ACCMODE, which we already > masked out above. > > On some systems, we have: > O_RDONLY == 0 > O_WRONLY == 1 > O_RDWR == 2 > > On other systems, we have: > O_RDONLY == 1 > O_WRONLY == 2 > O_RDWR == 3 > > Either way, if the user passed in O_RDWR, (flags & O_RDONLY) == O_RDONLY > returns true, which is wrong. > > O_ACCMODE was historically 0x3, although now that POSIX has O_EXEC and > O_SEARCH (which can be the same bit pattern), some systems now make > O_ACCMODE occupy 3 bits instead of 2. > Thanks for catching that and for the explanation. Looking at it again, the very minor optimisation of converting the 2nd 'if' to an 'else if' has the useful side-effect of appeasing the static analysis tool. --- a/io/channel-command.c +++ b/io/channel-command.c @@ -61,8 +61,7 @@ qio_channel_command_new_spawn(const char *const argv[], if (flags == O_RDONLY) { stdinnull = true; - } - if (flags == O_WRONLY) { + } else if (flags == O_WRONLY) { stdoutnull = true; } Regards, Liam
On 08/31/2018 10:36 AM, Liam Merwick wrote: > On 30/08/2018 17:18, Eric Blake wrote: >> On 08/30/2018 10:47 AM, Liam Merwick wrote: >>> Incorrect checking of flags could result in uninitialized >>> file descriptor being used. >>> > > Looking at it again, the very minor optimisation of converting the 2nd > 'if' to an 'else if' has the useful side-effect of appeasing the static > analysis tool. I never figured out what the tool precisely thought was wrong in the first place. Can you paste the output of the tool to see exactly what it analyzed as the potential flaw? Perhaps the analyzer was trying to see what would happen if a caller submitting the fourth value (3 on systems where O_RDONLY is 0, 0 on systems where O_RDONLY is 1) and the code not behaving in that setup, even though we know that all callers only submit the three valid values and never the fourth invalid value? Or maybe it's a weakness where we have made dependent assumptions but in independent branches, in such a way that we know it will never fail but the analyzer doesn't? > > --- a/io/channel-command.c > +++ b/io/channel-command.c > @@ -61,8 +61,7 @@ qio_channel_command_new_spawn(const char *const argv[], > > if (flags == O_RDONLY) { > stdinnull = true; > - } > - if (flags == O_WRONLY) { > + } else if (flags == O_WRONLY) { But this sort of change is acceptable, especially if it does shut up the analyzer, and done with a commit message mentioning that it is not a semantic change but does make it easier to use the static checker to find real problems by getting rid of noise. > stdoutnull = true; > } > > Regards, > Liam >
On 31/08/18 16:50, Eric Blake wrote: > On 08/31/2018 10:36 AM, Liam Merwick wrote: >> On 30/08/2018 17:18, Eric Blake wrote: >>> On 08/30/2018 10:47 AM, Liam Merwick wrote: >>>> Incorrect checking of flags could result in uninitialized >>>> file descriptor being used. >>>> > >> >> Looking at it again, the very minor optimisation of converting the >> 2nd 'if' to an 'else if' has the useful side-effect of appeasing the >> static analysis tool. > > I never figured out what the tool precisely thought was wrong in the > first place. Can you paste the output of the tool to see exactly what > it analyzed as the potential flaw? Perhaps the analyzer was trying to > see what would happen if a caller submitting the fourth value (3 on > systems where O_RDONLY is 0, 0 on systems where O_RDONLY is 1) and the > code not behaving in that setup, even though we know that all callers > only submit the three valid values and never the fourth invalid > value? Or maybe it's a weakness where we have made dependent > assumptions but in independent branches, in such a way that we know it > will never fail but the analyzer doesn't? > The specific error it reported was Error: File Invalid File Descriptor not Initialized [file-desc-not-init]: The value <unknown> is not initialized as a file descriptor. at line 91 of io/channel-command.c in function 'qio_channel_command_new_spawn'. resource not initialized when flags != 0 at line 62 and flags != 1 at line 65 and stdinnull is false at line 69 and stdoutnull is false at line 69. I've been staring at the code and can see no reason why it isn't a false positive (and I'll let the tool authors know) Regards, Liam >> >> --- a/io/channel-command.c >> +++ b/io/channel-command.c >> @@ -61,8 +61,7 @@ qio_channel_command_new_spawn(const char *const >> argv[], >> >> if (flags == O_RDONLY) { >> stdinnull = true; >> - } >> - if (flags == O_WRONLY) { >> + } else if (flags == O_WRONLY) { > > But this sort of change is acceptable, especially if it does shut up > the analyzer, and done with a commit message mentioning that it is not > a semantic change but does make it easier to use the static checker to > find real problems by getting rid of noise. > >> stdoutnull = true; >> } >> >> Regards, >> Liam >> >
On 08/31/2018 11:19 AM, Liam Merwick wrote: >>> Looking at it again, the very minor optimisation of converting the >>> 2nd 'if' to an 'else if' has the useful side-effect of appeasing the >>> static analysis tool. >> >> I never figured out what the tool precisely thought was wrong in the >> first place. Can you paste the output of the tool to see exactly what >> it analyzed as the potential flaw? Thanks. >> Perhaps the analyzer was trying to >> see what would happen if a caller submitting the fourth value (3 on >> systems where O_RDONLY is 0, 0 on systems where O_RDONLY is 1) and the >> code not behaving in that setup, even though we know that all callers >> only submit the three valid values and never the fourth invalid >> value? Or maybe it's a weakness where we have made dependent >> assumptions but in independent branches, in such a way that we know it >> will never fail but the analyzer doesn't? >> > > The specific error it reported was > > Error: File Invalid > File Descriptor not Initialized [file-desc-not-init]: > The value <unknown> is not initialized as a file descriptor. > at line 91 of io/channel-command.c in function > 'qio_channel_command_new_spawn'. > resource not initialized when flags != 0 at line 62 flags is not O_RDONLY, > and flags != 1 at line 65 flags is not O_WRONLY, > and stdinnull is false at line 69 > and stdoutnull is false at line 69. so yes, both stdinnull and stdoutnull will be false at this point. Based on our earlier masking, that means flags is either O_RDWR (2) or bogus (3). The analyzer is then complaining about: dup2(stdinnull ? devnull : stdinfd[0], STDIN_FILENO); So either it is complaining about devnull being uninitialized (which it is when flags is O_RDWR - but in that situation, we know stdinnull is false which also implies that the left half of ?: is unreachable and therefore shouldn't matter) or it is complaining about stdinfd[0] being uninitialized (but we can't reach here without having gone through the earlier (!stdinnull && pipe(stdinfd)), and again, given that stdinnull is false, that is initialized). The fact that it lists '<unknown>' rather than the actual (sub-)expression that it claims is uninitialized doesn't help. So yeah, I'm seriously confused as to why this false positive is being reported, or why the change from 'if/if' to 'if/else if' shuts it up. > > > I've been staring at the code and can see no reason why it isn't a false > positive (and I'll let the tool authors know) Thanks for the followup.
diff --git a/io/channel-command.c b/io/channel-command.c index 3e7eb17eff54..38deb687da21 100644 --- a/io/channel-command.c +++ b/io/channel-command.c @@ -59,10 +59,10 @@ qio_channel_command_new_spawn(const char *const argv[], flags = flags & O_ACCMODE; - if (flags == O_RDONLY) { + if ((flags & O_RDONLY) == O_RDONLY) { stdinnull = true; } - if (flags == O_WRONLY) { + if ((flags & O_WRONLY) == O_WRONLY) { stdoutnull = true; }