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

Fix integer overflow in ChunkingPlugin #8112

Merged
merged 1 commit into from
Mar 9, 2018
Merged

Conversation

MorrisJobke
Copy link
Member

Avoids errors when the size exceeds MAX_INT because of the cast to int. Better cast it to float to avoid this.

Fix #7948 and #8109 (comment)

@nariox @pasxalisk Could you check if this fixes the issue for you?

Copy link
Member

@rullzer rullzer left a comment

Choose a reason for hiding this comment

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

Ah 32bit systems?

@codecov
Copy link

codecov bot commented Jan 30, 2018

Codecov Report

Merging #8112 into master will increase coverage by <.01%.
The diff coverage is 100%.

@@             Coverage Diff              @@
##             master    #8112      +/-   ##
============================================
+ Coverage     51.88%   51.88%   +<.01%     
  Complexity    25352    25352              
============================================
  Files          1606     1606              
  Lines         95069    95069              
  Branches       1378     1378              
============================================
+ Hits          49326    49328       +2     
+ Misses        45743    45741       -2
Impacted Files Coverage Δ Complexity Δ
apps/dav/lib/Upload/ChunkingPlugin.php 96.29% <100%> (ø) 8 <0> (ø) ⬇️
lib/private/Files/Cache/Propagator.php 94.93% <0%> (-1.27%) 16% <0%> (ø)
lib/private/Files/ObjectStore/SwiftFactory.php 56.32% <0%> (+3.44%) 35% <0%> (ø) ⬇️

@MorrisJobke
Copy link
Member Author

Ah 32bit systems?

Exactly - some raspberry Pis and stuff like that.

@pasxalisk
Copy link

@MorrisJobke For me it does fix it.
I managed to upload a single file 45gb on my RPi 3 running Nextcloud 13 RC3.

@pasxalisk
Copy link

@MorrisJobke
EDIT: It did work with the solution i posted.
When i tried the above i saw a "Unkniwn CSync Error" on the windows client.

@pasxalisk
Copy link

@MorrisJobke
EDIT2: I tried again on a clean install and it seems that the fix is working as expected.
I am sorry for any inconvinience.

@ghost
Copy link

ghost commented Jan 30, 2018

@pasxalisk Raspberry Pi 3 use a 64 bit ARM CPU ( https://en.wikipedia.org/wiki/ARM_Cortex-A53 ) can you check on another ARM CPU please?

@pasxalisk
Copy link

@voxdemonix true. It does use a x64 but the raspbian OS is 32bits.
Unfortunatelly i don't own an ARM x64 OS device to test it.

@@ -99,7 +99,7 @@ private function verifySize() {
return;
}
$actualSize = $this->sourceNode->getSize();
if ((int)$expectedSize !== $actualSize) {
if ((float)$expectedSize !== (float)$actualSize) {
Copy link
Contributor

@go2sh go2sh Jan 31, 2018

Choose a reason for hiding this comment

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

This actually might create some very very tricky corner cases, where two not equal numbers get casted to the same float value, I think.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you have an example? Currently this is exactly the case that one number is the float (because it can store higher numbers) and the other one is casted to integer and caused an overflow there and thus is not the same.

Copy link
Contributor

Choose a reason for hiding this comment

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

I created a small test program and with higher numbers there a ton of collisions. And it must be: Int has 31 bits for the actual number and a float only 23. See my other comment.

Choose a reason for hiding this comment

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

Why you have to cast the values? String comparison not good enough?

If not, maybe bcmath/gmp should be used on 32bit systems.

Copy link
Member Author

Choose a reason for hiding this comment

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

@icewind1991 @rullzer @nickvergessen What do you think about the cast to string? Is this the better approach?

@go2sh
Copy link
Contributor

go2sh commented Jan 31, 2018

Test program in c#:

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            for(i=0;i<= 2147483647;i++)
            {
                int j;
                float a = (float)i;
                for (j=i-128;j <= i+128;j++)
                {
                    float b = (float)j;
                    if (a == b && i != j)
                    {
                        System.Console.Out.WriteLine(String.Format("i: {0}, j: {1}, a: {2}, b: {3}", i,j,a,b));
                    }
                }
            }
        }
    }
}

Result:

...
i: 16782085, j: 16782084, a: 1,678208E+07, b: 1,678208E+07
i: 16782087, j: 16782088, a: 1,678209E+07, b: 1,678209E+07
i: 16782087, j: 16782089, a: 1,678209E+07, b: 1,678209E+07
i: 16782088, j: 16782087, a: 1,678209E+07, b: 1,678209E+07
i: 16782088, j: 16782089, a: 1,678209E+07, b: 1,678209E+07
i: 16782089, j: 16782087, a: 1,678209E+07, b: 1,678209E+07
i: 16782089, j: 16782088, a: 1,678209E+07, b: 1,678209E+07
i: 16782091, j: 16782092, a: 1,678209E+07, b: 1,678209E+07
i: 16782091, j: 16782093, a: 1,678209E+07, b: 1,678209E+07
i: 16782092, j: 16782091, a: 1,678209E+07, b: 1,678209E+07
i: 16782092, j: 16782093, a: 1,678209E+07, b: 1,678209E+07
i: 16782093, j: 16782091, a: 1,678209E+07, b: 1,678209E+07
i: 16782093, j: 16782092, a: 1,678209E+07, b: 1,678209E+07
i: 16782095, j: 16782096, a: 1,67821E+07, b: 1,67821E+07
i: 16782095, j: 16782097, a: 1,67821E+07, b: 1,67821E+07
i: 16782096, j: 16782095, a: 1,67821E+07, b: 1,67821E+07
i: 16782096, j: 16782097, a: 1,67821E+07, b: 1,67821E+07
i: 16782097, j: 16782095, a: 1,67821E+07, b: 1,67821E+07
i: 16782097, j: 16782096, a: 1,67821E+07, b: 1,67821E+07
i: 16782099, j: 16782100, a: 1,67821E+07, b: 1,67821E+07
...

@lnicola
Copy link

lnicola commented Jan 31, 2018

@go2sh I don't know what float and int usually are in PHP, but a C# float is 32-bit, the same as an int. So large ints will will map to the same float.

The PHP documentation seems to imply that float is sometimes double, which would work fine for this purpose, but it's probably not true on 32-bit ARM systems.

@lnicola
Copy link

lnicola commented Jan 31, 2018

On the other hand, this seems more like an extra safety check (a file size match won't actually guarantee that the data was received correctly. Worst case, an error won't be reported when it should, but this won't prevent uploads from succeeding.

@go2sh
Copy link
Contributor

go2sh commented Jan 31, 2018

This very tricky... I have no proper solution.
@MorrisJobke You mean getSize() returns a float anyway, if its bigger than 2 GB on 32-bit systems?

@nariox
Copy link

nariox commented Jan 31, 2018

Thank you for the patch @MorrisJobke , it does "solve" my problem.

For those interested, PHP only has INTs and FLOATs defined, but whether they are 32-bit or 64-bit depends on the platform. The default behavior in PHP is to (quietly) convert INTs to FLOATs if they exceed PHP_INT_MAX.

It seems like there is a proposal to introduce BIGINTs to PHP (https://wiki.php.net/rfc/bigint), but not implemented yet.

The main problem with both approaches (casting to floats or ints) is that we lose precision. Floats would miss "small" errors, where the chunk size difference is beyond the precision, while ints fail in 32-bit int implementations, which the overflow would allow "large" chunk size errors to pass. Maybe a better approach would be to compare both (and casting ints, so we are only comparing the LSBs)? (Or to convert both to string, although I don't know to prevent overflow errors from happening before that)

@nariox
Copy link

nariox commented Jan 31, 2018

By the way, wouldn't it be better to checksum the files instead? This might require some changes in the client, but would give us a better check altogether than just chunk sizes and avoid the 32/64 bit debacle.

@lnicola
Copy link

lnicola commented Jan 31, 2018

This doesn't affect me, and I don't know the full context, but I'm thinking that:

  • the check isn't that useful and if two different floats compare as equal it's just a false negative; maybe it can be omitted on 32-bit systems
  • if the client is a browser, JavaScript has double precision support, so it can either compute the number of chunks, or send the size as a pair of integers (high and low part)
  • if the client is PHP, though luck
  • if the client is another application, this check might be annoying; one use case I have in mind is streaming a tar archive to the NextCloud server — the client won't know the total size beforehand.

@davidpoza
Copy link

@pasxalisk

@MorrisJobke For me it does fix it.I managed to upload a single file 45gb on my RPi 3 running Nextcloud 13 RC3.

What OS are you running in the rpi3? I'm using hypriotOS (debian based) and cannot pass over 2GB. I've patched the code.

Thx

@MorrisJobke MorrisJobke added the 2. developing Work in progress label Feb 26, 2018
Avoids errors when the size exceeds MAX_INT because of the cast to int. Better cast it to float to avoid this.

Signed-off-by: Morris Jobke <hey@morrisjobke.de>
@MorrisJobke MorrisJobke force-pushed the fix-integer-overflow branch from 3d670a0 to fc4e050 Compare March 6, 2018 17:47
@MorrisJobke
Copy link
Member Author

I updated this to be a cast to string. @pasxalisk @davidpoza @nariox Could you try the new patch and check if this works properly for you?

@MorrisJobke MorrisJobke added 3. to review Waiting for reviews and removed 2. developing Work in progress labels Mar 6, 2018
@MorrisJobke
Copy link
Member Author

MorrisJobke commented Mar 6, 2018

@maanloper and @Danny3 reported that this already works in #7948 (comment)

@nariox
Copy link

nariox commented Mar 7, 2018

I'm now having a different issue, but not sure if it is part of this (seems to be).
My uploads fail with "Sabre\DAV\Exception: Error while copying file to target location (copied bytes: 2147483647, expected filesize: )"

2147483647 is 2³¹-1

The particular exception is being thrown by /apps/dav/lib/Connector/Sabre/File.php at line 178:
throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
Commenting out this line leads to "successful" uploads, but files are cropped to 2GiB. My guess (as a non-PHP dev, is that casting the $expected to int causes it to be empt (maybe casting to strings might help?), but the copied bytes might need some work.

Since other people don't seem to having this problem, my guess is that I might want to file a new bug. But let me know what happens to you guys.

@MorrisJobke
Copy link
Member Author

Let's get this in

@MorrisJobke MorrisJobke merged commit ed50085 into master Mar 9, 2018
@MorrisJobke MorrisJobke deleted the fix-integer-overflow branch March 9, 2018 09:27
@MorrisJobke
Copy link
Member Author

backport to stable13 is in #8752

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3. to review Waiting for reviews bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Chunks on server do not sum up to X but to X (same number)
9 participants