[PATCH] Fix 32-bit overflow in Divesoft Freedom time handling
Dirk Hohndel
dirk at hohndel.org
Fri Oct 2 19:02:34 PDT 2015
I am so glad someone is keeping me honest.
Thank you, Linus.
Yes, I completely misunderstood what coverity was trying to tell me.
Oops.
I mainly want to clean out all the noise from the Coverity reports so they
become more useful. And while I ignored as "false positive" a ton of crap
where it was obviously just crap - a few of them seemed reasonable enough
to try and fix them (and I hope I got the other fixes right). And of
course it did find a couple of bugs that we had missed so far.
Again, thanks
/D
On Fri, Oct 02, 2015 at 09:57:04PM -0400, Linus Torvalds wrote:
>
> From: Linus Torvalds <torvalds at linux-foundation.org>
> Date: Fri, 2 Oct 2015 21:26:32 -0400
> Subject: [PATCH] Fix 32-bit overflow in Divesoft Freedom time handling
>
> Commit 31fb2e4c62ab ("Avoid possible sign extension") handled the
> problem when a "unsigned char" is shifted 24 bits left, and becomes a
> "signed int". By casting the result to uint32_t, that signed case won't
> happen.
>
> However, there were two bugs in that fix.
>
> The first is the comment. It's not that "timestamp_t" is signed that is
> the problem. No, the problem is inherent in the C expression
>
> (ptr[11] << 24)
>
> where "ptr[11]" is an unsigned char. In C arithmetic, unsigned char is
> implicitly type-expanded to "int", so while it has a value between
> 0..255, when you shift it left by 24, you can get a *negative* "int" as
> a result.
>
> So it's actually "ptr[11]" that should have been cast to "unsigned", but
> it so happens that you can do all the shifting and adding in "int", and
> then cast the end result to "uint32_t" and you'll get the same value.
> But at no point did "timestamp_t" matter.
>
> The other bug was pre-existing and just not fixed. When the code does
> the "+ 946684800" (to turn the timestamp to be seconds from the start of
> 2000, into seconds since the "unix epoch", ie 1970) that arithmetic is
> now done in that "uint32_t" (and used to be done in "int").
>
> Which means that the addition can overflow in 32 bits *before* it is
> cast to timestamp_t (which is 64 bits).
>
> Admittedly that 32-bit overflow happens a bit later than the sign bit
> gets set, but if we're worried aboout overflows, let's just do this
> right.
>
> In other words, we have a 32-bit unsigned offset since Jan 1, 2000, and
> for the full range we need to do the epoch correction in 32 bits.
> Because otherwise you fail in the year 2106 (32-bit unsigned unix epoch
> time limit), even though the 32-bit seconds *should* work all the way
> until the year 2136.
>
> Of course, I'll be rather surprised if people still use the Divesoft
> Freedom in the year 2106. Or rather, I won't be surprised, because I'll
> be dead.
>
> But if we think that the signed problem matters (in the year 2068), then
> dammit, we can extend it another 30 years.
>
> Signed-off-by: Linus Torvalds <torvalds at linux-foundation.org>
> ---
>
> This patch is entirely untested, and completely unnecessary. But it came
> about when I was looking at the recent commits, and went "Hmm, that's not
> right". The comment in particular is just confusingly wrong.
>
> Of course, it's not at all clear that the 32-bit number is actually
> unsigned to begin with. Maybe it's meant to be signed, the way
> traditional 32-bit unix time_t is. Maybe the Divesoft Freedom was
> designed to also be able to import dives from before Jan 1, 2000. Who
> knows? Not me. I've never seen one of those things.
>
> Even so, the addition of "946684800" (to correct between unix epoch and an
> epoch based on Jan 1, 2000) should be done in "timestamp_t".
>
> In other words, this patch is silly. But the comment really is wrong, and
> misses what Coverity was actually complaining about, and the code gets the
> right answer (well, until 2106) almost by accident.
>
> parse-xml.c | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/parse-xml.c b/parse-xml.c
> index 46261e9c9ef5..1aaeb42fe33e 100644
> --- a/parse-xml.c
> +++ b/parse-xml.c
> @@ -3269,9 +3269,11 @@ int parse_dlf_buffer(unsigned char *buffer, size_t size)
> snprintf(serial, sizeof(serial), "%d", (ptr[7] << 8) + ptr[6]);
> cur_dc->serial = strdup(serial);
> // Dive start time in seconds since 2000-01-01 00:00
> - // let's avoid implicit sign extensions (as timestamp_t is signed)
> + // let's avoid implicit sign extensions
> + // ("unsigned char" is expanded to "int" in C arithmetic, so
> + // (ptr[11] << 24) can be a signed integer)
> uint32_t offset = (ptr[11] << 24) + (ptr[10] << 16) + (ptr[9] << 8) + ptr[8];
> - cur_dc->when = offset + 946684800;
> + cur_dc->when = (timestamp_t) offset + 946684800;
> cur_dive->when = cur_dc->when;
>
> cur_dc->duration.seconds = ((ptr[14] & 0xFE) << 16) + (ptr[13] << 8) + ptr[12];
> --
> 2.6.0
>
More information about the subsurface
mailing list