@@ -390,12 +390,12 @@ static HFileBlock createFromBuff(ByteBuff buf, boolean usesHBaseChecksum, final
390
390
391
391
/**
392
392
* 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.
395
395
* @return Size of the block with header included.
396
396
*/
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 );
399
399
}
400
400
401
401
/**
@@ -1568,33 +1568,48 @@ public HFileBlock readBlockData(long offset, long onDiskSizeWithHeaderL, boolean
1568
1568
}
1569
1569
1570
1570
/**
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.
1572
1574
*/
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 ;
1582
1590
}
1583
- return ( int ) onDiskSizeWithHeaderL ;
1591
+ return true ;
1584
1592
}
1585
1593
1586
1594
/**
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.
1589
1598
*/
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 ;
1597
1607
}
1608
+ if (intValue == -1 ) {
1609
+ // a magic value we expect to see.
1610
+ return true ;
1611
+ }
1612
+ return checkOnDiskSizeWithHeader (intValue );
1598
1613
}
1599
1614
1600
1615
/**
@@ -1625,14 +1640,16 @@ private void cacheNextBlockHeader(final long offset, ByteBuff onDiskBlock,
1625
1640
this .prefetchedHeader .set (ph );
1626
1641
}
1627
1642
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 ;
1636
1653
}
1637
1654
1638
1655
private ByteBuff allocate (int size , boolean intoHeap ) {
@@ -1658,17 +1675,21 @@ private ByteBuff allocate(int size, boolean intoHeap) {
1658
1675
protected HFileBlock readBlockDataInternal (FSDataInputStream is , long offset ,
1659
1676
long onDiskSizeWithHeaderL , boolean pread , boolean verifyChecksum , boolean updateMetrics ,
1660
1677
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 ));
1661
1682
if (offset < 0 ) {
1662
1683
throw new IOException ("Invalid offset=" + offset + " trying to read " + "block (onDiskSize="
1663
1684
+ onDiskSizeWithHeaderL + ")" );
1664
1685
}
1686
+ if (!checkCallerProvidedOnDiskSizeWithHeader (onDiskSizeWithHeaderL )) {
1687
+ LOG .trace ("Caller provided invalid onDiskSizeWithHeaderL={}" , onDiskSizeWithHeaderL );
1688
+ onDiskSizeWithHeaderL = -1 ;
1689
+ }
1690
+ int onDiskSizeWithHeader = (int ) onDiskSizeWithHeaderL ;
1665
1691
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
1672
1693
// and will save us having to seek the stream backwards to reread the header we
1673
1694
// read the last time through here.
1674
1695
ByteBuff headerBuf = getCachedHeader (offset );
@@ -1682,8 +1703,8 @@ protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
1682
1703
// file has support for checksums (version 2+).
1683
1704
boolean checksumSupport = this .fileContext .isUseHBaseChecksum ();
1684
1705
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
1687
1708
// not cached (see getCachedHeader above), need to seek to pull it in. This is costly
1688
1709
// and should happen very rarely. Currently happens on open of a hfile reader where we
1689
1710
// 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,
1700
1721
}
1701
1722
onDiskSizeWithHeader = getOnDiskSizeWithHeader (headerBuf , checksumSupport );
1702
1723
}
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
+
1703
1737
int preReadHeaderSize = headerBuf == null ? 0 : hdrSize ;
1704
1738
// Allocate enough space to fit the next block's header too; saves a seek next time through.
1705
1739
// onDiskBlock is whole block + header + checksums then extra hdrSize to read next header;
@@ -1716,19 +1750,49 @@ protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
1716
1750
boolean readNextHeader = readAtOffset (is , onDiskBlock ,
1717
1751
onDiskSizeWithHeader - preReadHeaderSize , true , offset + preReadHeaderSize , pread );
1718
1752
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
+ }
1721
1763
if (headerBuf == null ) {
1722
1764
headerBuf = onDiskBlock .duplicate ().position (0 ).limit (hdrSize );
1723
1765
}
1724
- // Do a few checks before we go instantiate HFileBlock.
1725
- assert onDiskSizeWithHeader > this .hdrSize ;
1726
- verifyOnDiskSizeMatchesHeader (onDiskSizeWithHeader , headerBuf , offset , checksumSupport );
1766
+
1727
1767
ByteBuff curBlock = onDiskBlock .duplicate ().position (0 ).limit (onDiskSizeWithHeader );
1728
1768
// Verify checksum of the data before using it for building HFileBlock.
1729
1769
if (verifyChecksum && !validateChecksum (offset , curBlock , hdrSize )) {
1770
+ invalidateNextBlockHeader ();
1771
+ span .addEvent ("Falling back to HDFS checksumming." , attributesBuilder .build ());
1730
1772
return null ;
1731
1773
}
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
+
1732
1796
// remove checksum from buffer now that it's verified
1733
1797
int sizeWithoutChecksum = curBlock .getInt (Header .ON_DISK_DATA_SIZE_WITH_HEADER_INDEX );
1734
1798
curBlock .limit (sizeWithoutChecksum );
0 commit comments