-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Fixes reading past end of buffer for Google Earth terrain packets. #9519
Conversation
Thank you so much for the pull request @markaubin! I noticed this is your first pull request and I wanted to say welcome to the Cesium community! The Pull Request Guidelines is a handy reference for making sure your PR gets accepted quickly, so make sure to skim that.
Reviewers, don't forget to make sure that:
|
@tfili Do you have any thoughts on these changes? Not sure if it's fine to have the option of stopping before reading all four parts of a tile. |
@ebogo1 I need a little more info. It sounds like this code is protecting us from reading a incomplete tile. That is probably good to handle regardless. We definitely need a little more background and a test. |
var advanceTile = function (pos) { | ||
for (var quad = 0; quad < 4; ++quad) { | ||
// Guard against reading past the end of buffer. | ||
if (pos > totalSize) { |
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 prevents us from reading a part of a tile that doesn't exist, but if the 4th part is incomplete, the returned value will be out of range and it is used in the buffer.slice
call below.
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 block returns a -1, which is tested against below and will break out of the while loop.
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.
That will only account for the tile if there are less than 4 parts, but all parts are complete, which seems like it may be valid data. Is that correct?
My question was, do we need to account for partial quads, which I'm assuming would be an error? In that case, it would still fail.
For instance if a tile has a buffer of 98 bytes, but each part says it is 25 bytes. The function would read the 4th quad's size and return 100 and the slice would be out of bounds.
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 cannot say if partial quads are valid. From the existing code, it appears they are not. I think you are concerned with a malformed buffer which is a valid point and I will adjust the code to test immediately after the getUint32() call instead of before it. However in practice, that is not the problem we are experiencing. There seems to be extra bytes in the buffer after all valid tiles have been extracted.
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.
After some experimentation with partial tiles, and examining code that uses uses these terrain tiles, I'm quite certain that partial tiles are not supported.
The method interpolateHeight() assumes the terrain buffer has 4 parts without ever checking for buffer overruns.
function interpolateHeight(terrainData, u, v, rectangle) { |
Thanks @markaubin It looks like a worthwhile change but we need an example of what triggered this issue and a unit test. I have the one other comment to be addressed as well. |
This processTerrain() method was written with the assumption that the packet contains n tiles, where each tile contains exactly 4 subparts. When this isn't true, which is frequently the case with the current live legacy Google Earth servers, the DataView.getUint32() call will throw an exception. This makes the entire packet fail parsing. |
// Guard against reading past the end of buffer. Partial tiles are not | ||
// allowed. | ||
var size = dv.getUint32(pos, true); | ||
if (size > totalSize) { |
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.
Wouldn't this be
if ((pos+sizeOfUint32+size) > totalSize) {
because size
is just the size of the quad and will always be less than totalSize
.
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.
Actually size is either a valid number of bytes for the tile subpart (quad) when the buffer still has tiles left to be parsed out, or it will be something else completely and this can easily be found because it is not a small number. The extra bytes in the terrain packet buffer are used for something else completely and not relevant to the CesiumJS viewer. Perhaps making the test as you suggest is slightly more correct and I will try that out.
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.
So after more digging in the legacy Google Earth client code, I have learned that the terrain packet will always have 5 tiles, and each tile will always have 4 sub-meshes. Any other combination means something went wrong and the parsing should be aborted. The remaining bytes after the 20 sub-meshes will be water surface meshes, which appear to be unused by CesiumJS so should be ignored. I will rework the parsing with this understanding.
@markaubin looks like there are two failing tests with the recent changes:
|
From offline discussion, the test data that we have in Specs/Data/GoogleEarthEnterprise only has one tile with four sub-meshes and isn't conformant based on @markaubin's findings in #9519 (comment). Looking back at the PR history and it seems like the original Anyways here's the original file that works: gee.terrain.txt (remove the .txt extension). @markaubin once you update that file, let us know and we can get this merged. |
The previous test terrain contained only one mesh which worked fine with the processTerrain() method in decodeGoogleEarthEnterprisePacket.js because it wasn't requiring the correct number (5). Now that the parsing code has been updated to expect all meshes, the test data needs to be updated. This file was downloaded directly from a Google internal enterprise server. The location was somewhere near Hawaii so that it would contain terrain above and below sea level, plus contain water surface meshes. The URL path of the downloaded file was: flatfile?f1c-0300022-t.899 These water surface meshes are not currently used by CesiumJS. They were added to the terrain packet format after the processTerrain() method was first written which explains why it started failing with a later packet format.
@markaubin one last thing, could you update |
Thanks @markaubin! |
@markaubin congrats on your first CesiumJS pull request, thanks again for contributing! |
Please review this fix to parsing Google Earth terrain packets.