@@ -392,12 +392,12 @@ static HFileBlock createFromBuff(ByteBuff buf, boolean usesHBaseChecksum, final
392392
393393 /**
394394 * Parse total on disk size including header and checksum.
395- * @param headerBuf Header ByteBuffer. Presumed exact size of header.
396- * @param verifyChecksum true if checksum verification is in use.
395+ * @param headerBuf Header ByteBuffer. Presumed exact size of header.
396+ * @param checksumSupport true if checksum verification is in use.
397397 * @return Size of the block with header included.
398398 */
399- private static int getOnDiskSizeWithHeader (final ByteBuff headerBuf , boolean verifyChecksum ) {
400- return headerBuf .getInt (Header .ON_DISK_SIZE_WITHOUT_HEADER_INDEX ) + headerSize (verifyChecksum );
399+ private static int getOnDiskSizeWithHeader (final ByteBuff headerBuf , boolean checksumSupport ) {
400+ return headerBuf .getInt (Header .ON_DISK_SIZE_WITHOUT_HEADER_INDEX ) + headerSize (checksumSupport );
401401 }
402402
403403 /**
@@ -1597,33 +1597,48 @@ public HFileBlock readBlockData(long offset, long onDiskSizeWithHeaderL, boolean
15971597 }
15981598
15991599 /**
1600- * Returns Check <code>onDiskSizeWithHeaderL</code> size is healthy and then return it as an int
1600+ * Check that {@code value} read from a block header seems reasonable, within a large margin of
1601+ * error.
1602+ * @return {@code true} if the value is safe to proceed, {@code false} otherwise.
16011603 */
1602- private static int checkAndGetSizeAsInt (final long onDiskSizeWithHeaderL , final int hdrSize )
1603- throws IOException {
1604- if (
1605- (onDiskSizeWithHeaderL < hdrSize && onDiskSizeWithHeaderL != -1 )
1606- || onDiskSizeWithHeaderL >= Integer .MAX_VALUE
1607- ) {
1608- throw new IOException (
1609- "Invalid onDisksize=" + onDiskSizeWithHeaderL + ": expected to be at least " + hdrSize
1610- + " and at most " + Integer .MAX_VALUE + ", or -1" );
1604+ private boolean checkOnDiskSizeWithHeader (int value ) {
1605+ if (value < 0 ) {
1606+ if (LOG .isTraceEnabled ()) {
1607+ LOG .trace (
1608+ "onDiskSizeWithHeader={}; value represents a size, so it should never be negative." ,
1609+ value );
1610+ }
1611+ return false ;
1612+ }
1613+ if (value - hdrSize < 0 ) {
1614+ if (LOG .isTraceEnabled ()) {
1615+ LOG .trace ("onDiskSizeWithHeader={}, hdrSize={}; don't accept a value that is negative"
1616+ + " after the header size is excluded." , value , hdrSize );
1617+ }
1618+ return false ;
16111619 }
1612- return ( int ) onDiskSizeWithHeaderL ;
1620+ return true ;
16131621 }
16141622
16151623 /**
1616- * Verify the passed in onDiskSizeWithHeader aligns with what is in the header else something is
1617- * not right.
1624+ * Check that {@code value} provided by the calling context seems reasonable, within a large
1625+ * margin of error.
1626+ * @return {@code true} if the value is safe to proceed, {@code false} otherwise.
16181627 */
1619- private void verifyOnDiskSizeMatchesHeader (final int passedIn , final ByteBuff headerBuf ,
1620- final long offset , boolean verifyChecksum ) throws IOException {
1621- // Assert size provided aligns with what is in the header
1622- int fromHeader = getOnDiskSizeWithHeader (headerBuf , verifyChecksum );
1623- if (passedIn != fromHeader ) {
1624- throw new IOException ("Passed in onDiskSizeWithHeader=" + passedIn + " != " + fromHeader
1625- + ", offset=" + offset + ", fileContext=" + this .fileContext );
1628+ private boolean checkCallerProvidedOnDiskSizeWithHeader (long value ) {
1629+ // same validation logic as is used by Math.toIntExact(long)
1630+ int intValue = (int ) value ;
1631+ if (intValue != value ) {
1632+ if (LOG .isTraceEnabled ()) {
1633+ LOG .trace ("onDiskSizeWithHeaderL={}; value exceeds int size limits." , value );
1634+ }
1635+ return false ;
1636+ }
1637+ if (intValue == -1 ) {
1638+ // a magic value we expect to see.
1639+ return true ;
16261640 }
1641+ return checkOnDiskSizeWithHeader (intValue );
16271642 }
16281643
16291644 /**
@@ -1654,14 +1669,16 @@ private void cacheNextBlockHeader(final long offset, ByteBuff onDiskBlock,
16541669 this .prefetchedHeader .set (ph );
16551670 }
16561671
1657- private int getNextBlockOnDiskSize (boolean readNextHeader , ByteBuff onDiskBlock ,
1658- int onDiskSizeWithHeader ) {
1659- int nextBlockOnDiskSize = -1 ;
1660- if (readNextHeader ) {
1661- nextBlockOnDiskSize =
1662- onDiskBlock .getIntAfterPosition (onDiskSizeWithHeader + BlockType .MAGIC_LENGTH ) + hdrSize ;
1663- }
1664- return nextBlockOnDiskSize ;
1672+ /**
1673+ * Clear the cached value when its integrity is suspect.
1674+ */
1675+ private void invalidateNextBlockHeader () {
1676+ prefetchedHeader .set (null );
1677+ }
1678+
1679+ private int getNextBlockOnDiskSize (ByteBuff onDiskBlock , int onDiskSizeWithHeader ) {
1680+ return onDiskBlock .getIntAfterPosition (onDiskSizeWithHeader + BlockType .MAGIC_LENGTH )
1681+ + hdrSize ;
16651682 }
16661683
16671684 private ByteBuff allocate (int size , boolean intoHeap ) {
@@ -1687,17 +1704,21 @@ private ByteBuff allocate(int size, boolean intoHeap) {
16871704 protected HFileBlock readBlockDataInternal (FSDataInputStream is , long offset ,
16881705 long onDiskSizeWithHeaderL , boolean pread , boolean verifyChecksum , boolean updateMetrics ,
16891706 boolean intoHeap ) throws IOException {
1707+ final Span span = Span .current ();
1708+ final AttributesBuilder attributesBuilder = Attributes .builder ();
1709+ Optional .of (Context .current ()).map (val -> val .get (CONTEXT_KEY ))
1710+ .ifPresent (c -> c .accept (attributesBuilder ));
16901711 if (offset < 0 ) {
16911712 throw new IOException ("Invalid offset=" + offset + " trying to read " + "block (onDiskSize="
16921713 + onDiskSizeWithHeaderL + ")" );
16931714 }
1715+ if (!checkCallerProvidedOnDiskSizeWithHeader (onDiskSizeWithHeaderL )) {
1716+ LOG .trace ("Caller provided invalid onDiskSizeWithHeaderL={}" , onDiskSizeWithHeaderL );
1717+ onDiskSizeWithHeaderL = -1 ;
1718+ }
1719+ int onDiskSizeWithHeader = (int ) onDiskSizeWithHeaderL ;
16941720
1695- final Span span = Span .current ();
1696- final AttributesBuilder attributesBuilder = Attributes .builder ();
1697- Optional .of (Context .current ()).map (val -> val .get (CONTEXT_KEY ))
1698- .ifPresent (c -> c .accept (attributesBuilder ));
1699- int onDiskSizeWithHeader = checkAndGetSizeAsInt (onDiskSizeWithHeaderL , hdrSize );
1700- // Try and get cached header. Will serve us in rare case where onDiskSizeWithHeaderL is -1
1721+ // Try to use the cached header. Will serve us in rare case where onDiskSizeWithHeaderL==-1
17011722 // and will save us having to seek the stream backwards to reread the header we
17021723 // read the last time through here.
17031724 ByteBuff headerBuf = getCachedHeader (offset );
@@ -1711,8 +1732,8 @@ protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
17111732 // file has support for checksums (version 2+).
17121733 boolean checksumSupport = this .fileContext .isUseHBaseChecksum ();
17131734 long startTime = EnvironmentEdgeManager .currentTime ();
1714- if (onDiskSizeWithHeader <= 0 ) {
1715- // We were not passed the block size. Need to get it from the header. If header was
1735+ if (onDiskSizeWithHeader == - 1 ) {
1736+ // The caller does not know the block size. Need to get it from the header. If header was
17161737 // not cached (see getCachedHeader above), need to seek to pull it in. This is costly
17171738 // and should happen very rarely. Currently happens on open of a hfile reader where we
17181739 // read the trailer blocks to pull in the indices. Otherwise, we are reading block sizes
@@ -1729,6 +1750,19 @@ protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
17291750 }
17301751 onDiskSizeWithHeader = getOnDiskSizeWithHeader (headerBuf , checksumSupport );
17311752 }
1753+
1754+ // The common case is that onDiskSizeWithHeader was produced by a read without checksum
1755+ // validation, so give it a sanity check before trying to use it.
1756+ if (!checkOnDiskSizeWithHeader (onDiskSizeWithHeader )) {
1757+ if (verifyChecksum ) {
1758+ invalidateNextBlockHeader ();
1759+ span .addEvent ("Falling back to HDFS checksumming." , attributesBuilder .build ());
1760+ return null ;
1761+ } else {
1762+ throw new IOException ("Invalid onDiskSizeWithHeader=" + onDiskSizeWithHeader );
1763+ }
1764+ }
1765+
17321766 int preReadHeaderSize = headerBuf == null ? 0 : hdrSize ;
17331767 // Allocate enough space to fit the next block's header too; saves a seek next time through.
17341768 // onDiskBlock is whole block + header + checksums then extra hdrSize to read next header;
@@ -1745,19 +1779,49 @@ protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
17451779 boolean readNextHeader = readAtOffset (is , onDiskBlock ,
17461780 onDiskSizeWithHeader - preReadHeaderSize , true , offset + preReadHeaderSize , pread );
17471781 onDiskBlock .rewind (); // in case of moving position when copying a cached header
1748- int nextBlockOnDiskSize =
1749- getNextBlockOnDiskSize (readNextHeader , onDiskBlock , onDiskSizeWithHeader );
1782+
1783+ // the call to validateChecksum for this block excludes the next block header over-read, so
1784+ // no reason to delay extracting this value.
1785+ int nextBlockOnDiskSize = -1 ;
1786+ if (readNextHeader ) {
1787+ int parsedVal = getNextBlockOnDiskSize (onDiskBlock , onDiskSizeWithHeader );
1788+ if (checkOnDiskSizeWithHeader (parsedVal )) {
1789+ nextBlockOnDiskSize = parsedVal ;
1790+ }
1791+ }
17501792 if (headerBuf == null ) {
17511793 headerBuf = onDiskBlock .duplicate ().position (0 ).limit (hdrSize );
17521794 }
1753- // Do a few checks before we go instantiate HFileBlock.
1754- assert onDiskSizeWithHeader > this .hdrSize ;
1755- verifyOnDiskSizeMatchesHeader (onDiskSizeWithHeader , headerBuf , offset , checksumSupport );
1795+
17561796 ByteBuff curBlock = onDiskBlock .duplicate ().position (0 ).limit (onDiskSizeWithHeader );
17571797 // Verify checksum of the data before using it for building HFileBlock.
17581798 if (verifyChecksum && !validateChecksum (offset , curBlock , hdrSize )) {
1799+ invalidateNextBlockHeader ();
1800+ span .addEvent ("Falling back to HDFS checksumming." , attributesBuilder .build ());
17591801 return null ;
17601802 }
1803+
1804+ // TODO: is this check necessary or can we proceed with a provided value regardless of
1805+ // what is in the header?
1806+ int fromHeader = getOnDiskSizeWithHeader (headerBuf , checksumSupport );
1807+ if (onDiskSizeWithHeader != fromHeader ) {
1808+ if (LOG .isTraceEnabled ()) {
1809+ LOG .trace ("Passed in onDiskSizeWithHeader={} != {}, offset={}, fileContext={}" ,
1810+ onDiskSizeWithHeader , fromHeader , offset , this .fileContext );
1811+ }
1812+ if (checksumSupport && verifyChecksum ) {
1813+ // This file supports HBase checksums and verification of those checksums was
1814+ // requested. The block size provided by the caller (presumably from the block index)
1815+ // does not match the block size written to the block header. treat this as
1816+ // HBase-checksum failure.
1817+ span .addEvent ("Falling back to HDFS checksumming." , attributesBuilder .build ());
1818+ invalidateNextBlockHeader ();
1819+ return null ;
1820+ }
1821+ throw new IOException ("Passed in onDiskSizeWithHeader=" + onDiskSizeWithHeader + " != "
1822+ + fromHeader + ", offset=" + offset + ", fileContext=" + this .fileContext );
1823+ }
1824+
17611825 // remove checksum from buffer now that it's verified
17621826 int sizeWithoutChecksum = curBlock .getInt (Header .ON_DISK_DATA_SIZE_WITH_HEADER_INDEX );
17631827 curBlock .limit (sizeWithoutChecksum );
0 commit comments