@@ -390,12 +390,12 @@ static HFileBlock createFromBuff(ByteBuff buf, boolean usesHBaseChecksum, final
390390
391391 /**
392392 * Parse total on disk size including header and checksum.
393- * @param headerBuf Header ByteBuffer. Presumed exact size of header.
394- * @param verifyChecksum true if checksum verification is in use.
393+ * @param headerBuf Header ByteBuffer. Presumed exact size of header.
394+ * @param checksumSupport true if checksum verification is in use.
395395 * @return Size of the block with header included.
396396 */
397- private static int getOnDiskSizeWithHeader (final ByteBuff headerBuf , boolean verifyChecksum ) {
398- return headerBuf .getInt (Header .ON_DISK_SIZE_WITHOUT_HEADER_INDEX ) + headerSize (verifyChecksum );
397+ private static int getOnDiskSizeWithHeader (final ByteBuff headerBuf , boolean checksumSupport ) {
398+ return headerBuf .getInt (Header .ON_DISK_SIZE_WITHOUT_HEADER_INDEX ) + headerSize (checksumSupport );
399399 }
400400
401401 /**
@@ -1568,33 +1568,48 @@ public HFileBlock readBlockData(long offset, long onDiskSizeWithHeaderL, boolean
15681568 }
15691569
15701570 /**
1571- * Returns Check <code>onDiskSizeWithHeaderL</code> size is healthy and then return it as an int
1571+ * Check that {@code value} read from a block header seems reasonable, within a large margin of
1572+ * error.
1573+ * @return {@code true} if the value is safe to proceed, {@code false} otherwise.
15721574 */
1573- private static int checkAndGetSizeAsInt (final long onDiskSizeWithHeaderL , final int hdrSize )
1574- throws IOException {
1575- if (
1576- (onDiskSizeWithHeaderL < hdrSize && onDiskSizeWithHeaderL != -1 )
1577- || onDiskSizeWithHeaderL >= Integer .MAX_VALUE
1578- ) {
1579- throw new IOException (
1580- "Invalid onDisksize=" + onDiskSizeWithHeaderL + ": expected to be at least " + hdrSize
1581- + " and at most " + Integer .MAX_VALUE + ", or -1" );
1575+ private boolean checkOnDiskSizeWithHeader (int value ) {
1576+ if (value < 0 ) {
1577+ if (LOG .isTraceEnabled ()) {
1578+ LOG .trace (
1579+ "onDiskSizeWithHeader={}; value represents a size, so it should never be negative." ,
1580+ value );
1581+ }
1582+ return false ;
1583+ }
1584+ if (value - hdrSize < 0 ) {
1585+ if (LOG .isTraceEnabled ()) {
1586+ LOG .trace ("onDiskSizeWithHeader={}, hdrSize={}; don't accept a value that is negative"
1587+ + " after the header size is excluded." , value , hdrSize );
1588+ }
1589+ return false ;
15821590 }
1583- return ( int ) onDiskSizeWithHeaderL ;
1591+ return true ;
15841592 }
15851593
15861594 /**
1587- * Verify the passed in onDiskSizeWithHeader aligns with what is in the header else something is
1588- * not right.
1595+ * Check that {@code value} provided by the calling context seems reasonable, within a large
1596+ * margin of error.
1597+ * @return {@code true} if the value is safe to proceed, {@code false} otherwise.
15891598 */
1590- private void verifyOnDiskSizeMatchesHeader (final int passedIn , final ByteBuff headerBuf ,
1591- final long offset , boolean verifyChecksum ) throws IOException {
1592- // Assert size provided aligns with what is in the header
1593- int fromHeader = getOnDiskSizeWithHeader (headerBuf , verifyChecksum );
1594- if (passedIn != fromHeader ) {
1595- throw new IOException ("Passed in onDiskSizeWithHeader=" + passedIn + " != " + fromHeader
1596- + ", offset=" + offset + ", fileContext=" + this .fileContext );
1599+ private boolean checkCallerProvidedOnDiskSizeWithHeader (long value ) {
1600+ // same validation logic as is used by Math.toIntExact(long)
1601+ int intValue = (int ) value ;
1602+ if (intValue != value ) {
1603+ if (LOG .isTraceEnabled ()) {
1604+ LOG .trace ("onDiskSizeWithHeaderL={}; value exceeds int size limits." , value );
1605+ }
1606+ return false ;
15971607 }
1608+ if (intValue == -1 ) {
1609+ // a magic value we expect to see.
1610+ return true ;
1611+ }
1612+ return checkOnDiskSizeWithHeader (intValue );
15981613 }
15991614
16001615 /**
@@ -1625,14 +1640,16 @@ private void cacheNextBlockHeader(final long offset, ByteBuff onDiskBlock,
16251640 this .prefetchedHeader .set (ph );
16261641 }
16271642
1628- private int getNextBlockOnDiskSize (boolean readNextHeader , ByteBuff onDiskBlock ,
1629- int onDiskSizeWithHeader ) {
1630- int nextBlockOnDiskSize = -1 ;
1631- if (readNextHeader ) {
1632- nextBlockOnDiskSize =
1633- onDiskBlock .getIntAfterPosition (onDiskSizeWithHeader + BlockType .MAGIC_LENGTH ) + hdrSize ;
1634- }
1635- return nextBlockOnDiskSize ;
1643+ /**
1644+ * Clear the cached value when its integrity is suspect.
1645+ */
1646+ private void invalidateNextBlockHeader () {
1647+ prefetchedHeader .set (null );
1648+ }
1649+
1650+ private int getNextBlockOnDiskSize (ByteBuff onDiskBlock , int onDiskSizeWithHeader ) {
1651+ return onDiskBlock .getIntAfterPosition (onDiskSizeWithHeader + BlockType .MAGIC_LENGTH )
1652+ + hdrSize ;
16361653 }
16371654
16381655 private ByteBuff allocate (int size , boolean intoHeap ) {
@@ -1658,17 +1675,21 @@ private ByteBuff allocate(int size, boolean intoHeap) {
16581675 protected HFileBlock readBlockDataInternal (FSDataInputStream is , long offset ,
16591676 long onDiskSizeWithHeaderL , boolean pread , boolean verifyChecksum , boolean updateMetrics ,
16601677 boolean intoHeap ) throws IOException {
1678+ final Span span = Span .current ();
1679+ final AttributesBuilder attributesBuilder = Attributes .builder ();
1680+ Optional .of (Context .current ()).map (val -> val .get (CONTEXT_KEY ))
1681+ .ifPresent (c -> c .accept (attributesBuilder ));
16611682 if (offset < 0 ) {
16621683 throw new IOException ("Invalid offset=" + offset + " trying to read " + "block (onDiskSize="
16631684 + onDiskSizeWithHeaderL + ")" );
16641685 }
1686+ if (!checkCallerProvidedOnDiskSizeWithHeader (onDiskSizeWithHeaderL )) {
1687+ LOG .trace ("Caller provided invalid onDiskSizeWithHeaderL={}" , onDiskSizeWithHeaderL );
1688+ onDiskSizeWithHeaderL = -1 ;
1689+ }
1690+ int onDiskSizeWithHeader = (int ) onDiskSizeWithHeaderL ;
16651691
1666- final Span span = Span .current ();
1667- final AttributesBuilder attributesBuilder = Attributes .builder ();
1668- Optional .of (Context .current ()).map (val -> val .get (CONTEXT_KEY ))
1669- .ifPresent (c -> c .accept (attributesBuilder ));
1670- int onDiskSizeWithHeader = checkAndGetSizeAsInt (onDiskSizeWithHeaderL , hdrSize );
1671- // Try and get cached header. Will serve us in rare case where onDiskSizeWithHeaderL is -1
1692+ // Try to use the cached header. Will serve us in rare case where onDiskSizeWithHeaderL==-1
16721693 // and will save us having to seek the stream backwards to reread the header we
16731694 // read the last time through here.
16741695 ByteBuff headerBuf = getCachedHeader (offset );
@@ -1682,8 +1703,8 @@ protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
16821703 // file has support for checksums (version 2+).
16831704 boolean checksumSupport = this .fileContext .isUseHBaseChecksum ();
16841705 long startTime = EnvironmentEdgeManager .currentTime ();
1685- if (onDiskSizeWithHeader <= 0 ) {
1686- // We were not passed the block size. Need to get it from the header. If header was
1706+ if (onDiskSizeWithHeader == - 1 ) {
1707+ // The caller does not know the block size. Need to get it from the header. If header was
16871708 // not cached (see getCachedHeader above), need to seek to pull it in. This is costly
16881709 // and should happen very rarely. Currently happens on open of a hfile reader where we
16891710 // read the trailer blocks to pull in the indices. Otherwise, we are reading block sizes
@@ -1700,6 +1721,19 @@ protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
17001721 }
17011722 onDiskSizeWithHeader = getOnDiskSizeWithHeader (headerBuf , checksumSupport );
17021723 }
1724+
1725+ // The common case is that onDiskSizeWithHeader was produced by a read without checksum
1726+ // validation, so give it a sanity check before trying to use it.
1727+ if (!checkOnDiskSizeWithHeader (onDiskSizeWithHeader )) {
1728+ if (verifyChecksum ) {
1729+ invalidateNextBlockHeader ();
1730+ span .addEvent ("Falling back to HDFS checksumming." , attributesBuilder .build ());
1731+ return null ;
1732+ } else {
1733+ throw new IOException ("Invalid onDiskSizeWithHeader=" + onDiskSizeWithHeader );
1734+ }
1735+ }
1736+
17031737 int preReadHeaderSize = headerBuf == null ? 0 : hdrSize ;
17041738 // Allocate enough space to fit the next block's header too; saves a seek next time through.
17051739 // onDiskBlock is whole block + header + checksums then extra hdrSize to read next header;
@@ -1716,19 +1750,49 @@ protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
17161750 boolean readNextHeader = readAtOffset (is , onDiskBlock ,
17171751 onDiskSizeWithHeader - preReadHeaderSize , true , offset + preReadHeaderSize , pread );
17181752 onDiskBlock .rewind (); // in case of moving position when copying a cached header
1719- int nextBlockOnDiskSize =
1720- getNextBlockOnDiskSize (readNextHeader , onDiskBlock , onDiskSizeWithHeader );
1753+
1754+ // the call to validateChecksum for this block excludes the next block header over-read, so
1755+ // no reason to delay extracting this value.
1756+ int nextBlockOnDiskSize = -1 ;
1757+ if (readNextHeader ) {
1758+ int parsedVal = getNextBlockOnDiskSize (onDiskBlock , onDiskSizeWithHeader );
1759+ if (checkOnDiskSizeWithHeader (parsedVal )) {
1760+ nextBlockOnDiskSize = parsedVal ;
1761+ }
1762+ }
17211763 if (headerBuf == null ) {
17221764 headerBuf = onDiskBlock .duplicate ().position (0 ).limit (hdrSize );
17231765 }
1724- // Do a few checks before we go instantiate HFileBlock.
1725- assert onDiskSizeWithHeader > this .hdrSize ;
1726- verifyOnDiskSizeMatchesHeader (onDiskSizeWithHeader , headerBuf , offset , checksumSupport );
1766+
17271767 ByteBuff curBlock = onDiskBlock .duplicate ().position (0 ).limit (onDiskSizeWithHeader );
17281768 // Verify checksum of the data before using it for building HFileBlock.
17291769 if (verifyChecksum && !validateChecksum (offset , curBlock , hdrSize )) {
1770+ invalidateNextBlockHeader ();
1771+ span .addEvent ("Falling back to HDFS checksumming." , attributesBuilder .build ());
17301772 return null ;
17311773 }
1774+
1775+ // TODO: is this check necessary or can we proceed with a provided value regardless of
1776+ // what is in the header?
1777+ int fromHeader = getOnDiskSizeWithHeader (headerBuf , checksumSupport );
1778+ if (onDiskSizeWithHeader != fromHeader ) {
1779+ if (LOG .isTraceEnabled ()) {
1780+ LOG .trace ("Passed in onDiskSizeWithHeader={} != {}, offset={}, fileContext={}" ,
1781+ onDiskSizeWithHeader , fromHeader , offset , this .fileContext );
1782+ }
1783+ if (checksumSupport && verifyChecksum ) {
1784+ // This file supports HBase checksums and verification of those checksums was
1785+ // requested. The block size provided by the caller (presumably from the block index)
1786+ // does not match the block size written to the block header. treat this as
1787+ // HBase-checksum failure.
1788+ span .addEvent ("Falling back to HDFS checksumming." , attributesBuilder .build ());
1789+ invalidateNextBlockHeader ();
1790+ return null ;
1791+ }
1792+ throw new IOException ("Passed in onDiskSizeWithHeader=" + onDiskSizeWithHeader + " != "
1793+ + fromHeader + ", offset=" + offset + ", fileContext=" + this .fileContext );
1794+ }
1795+
17321796 // remove checksum from buffer now that it's verified
17331797 int sizeWithoutChecksum = curBlock .getInt (Header .ON_DISK_DATA_SIZE_WITH_HEADER_INDEX );
17341798 curBlock .limit (sizeWithoutChecksum );
0 commit comments