From c655d2381525aef22fad6294051ac1036f89aa9e Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 15 Jun 2011 13:30:24 +0000 Subject: [PATCH 1/5] * Updates: - Add the router version to the zip file comment in the updater - Add a class to extract the zip file comment - Require the sud version header to match the zip file comment to prevent spoofing of the version number, since the version number in the header is not covered by the sud signature. --- build.xml | 95 +++++++++------ .../src/net/i2p/crypto/TrustedUpdate.java | 62 +++++++++- .../java/src/net/i2p/util/ZipFileComment.java | 111 ++++++++++++++++++ 3 files changed, 226 insertions(+), 42 deletions(-) create mode 100644 core/java/src/net/i2p/util/ZipFileComment.java diff --git a/build.xml b/build.xml index b1cff79de..b8f893172 100644 --- a/build.xml +++ b/build.xml @@ -316,31 +316,11 @@ - + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -704,15 +712,22 @@ - - + + + + - - + + + @@ -979,9 +994,9 @@ - + @@ -997,15 +1012,7 @@ - - - - - - - - - + @@ -1036,6 +1043,13 @@ + + + + + + + @@ -1062,6 +1076,13 @@ + + + + + + + diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java index 43ca16283..0b0b35b64 100644 --- a/core/java/src/net/i2p/crypto/TrustedUpdate.java +++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java @@ -22,6 +22,7 @@ import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.util.Log; import net.i2p.util.VersionComparator; +import net.i2p.util.ZipFileComment; /** *

Handles DSA signing and verification of update files. @@ -35,6 +36,7 @@ import net.i2p.util.VersionComparator; * java net.i2p.crypto.TrustedUpdate sign inputFile signedFile privateKeyFile version * java net.i2p.crypto.TrustedUpdate verifysig signedFile * java net.i2p.crypto.TrustedUpdate verifyupdate signedFile + * java net.i2p.crypto.TrustedUpdate verifyversion signedFile * * * @author jrandom and smeghead @@ -242,6 +244,8 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR ok = verifySigCLI(args[1]); } else if ("verifyupdate".equals(args[0])) { ok = verifyUpdateCLI(args[1]); + } else if ("verifyversion".equals(args[0])) { + ok = verifyVersionCLI(args[1]); } else { showUsageCLI(); } @@ -301,11 +305,12 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR } private static final void showUsageCLI() { - System.err.println("Usage: TrustedUpdate keygen publicKeyFile privateKeyFile"); - System.err.println(" TrustedUpdate showversion signedFile"); - System.err.println(" TrustedUpdate sign inputFile signedFile privateKeyFile version"); - System.err.println(" TrustedUpdate verifysig signedFile"); - System.err.println(" TrustedUpdate verifyupdate signedFile"); + System.err.println("Usage: TrustedUpdate keygen publicKeyFile privateKeyFile"); + System.err.println(" TrustedUpdate showversion signedFile"); + System.err.println(" TrustedUpdate sign inputFile signedFile privateKeyFile version"); + System.err.println(" TrustedUpdate verifysig signedFile"); + System.err.println(" TrustedUpdate verifyupdate signedFile"); + System.err.println(" TrustedUpdate verifyversion signedFile"); } /** @return success */ @@ -349,9 +354,29 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR System.out.println("File version is newer than current version."); else System.out.println("File version is older than or equal to current version."); + return isUpdate; } + /** + * @return true if there's no version mismatch + * @since 0.8.8 + */ + private static final boolean verifyVersionCLI(String signedFile) { + TrustedUpdate tu = new TrustedUpdate(); + File file = new File(signedFile); + // ignore result, just used to read in version + tu.isUpdatedVersion("0", file); + + boolean isMatch = tu.verifyVersionMatch(file); + if (isMatch) + System.out.println("Version verified"); + else + System.out.println("Version mismatch, header version does not match zip comment version"); + + return isMatch; + } + /** * Fetches the trusted keys for the current instance. * @@ -490,6 +515,13 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR * file's version is newer than the given current version, migrates the data * out of signedFile and into outputFile. * + * As of 0.8.8, the embedded file must be a zip file with + * a standard zip header and a UTF-8 zip file comment + * matching the version in the sud header. This prevents spoofing the version, + * since the sud signature does NOT cover the version in the header. + * (We do this for sud/su2 files but not plugin xpi2p files - + * don't use this method for plugin files) + * * @param currentVersion The current version to check against. * @param signedFile A signed update file. * @param outputFile The file to write the verified data to. @@ -507,12 +539,32 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR return "Downloaded version is not greater than current version"; } + if (!verifyVersionMatch(signedFile)) + return "Update file invalid - signed version mismatch"; + if (!verify(signedFile)) return "Unknown signing key or corrupt file"; return migrateFile(signedFile, outputFile); } + /** + * Verify the version in the sud header matches the version in the zip comment + * (and that the embedded file is a zip file at all) + * isUpdatedVersion() must be called first to set _newVersion. + * + * @return true if matches + * + * @since 0.8.8 + */ + private boolean verifyVersionMatch(File signedFile) { + try { + String zipComment = ZipFileComment.getComment(signedFile, VERSION_BYTES, HEADER_BYTES); + return zipComment.equals(_newVersion); + } catch (IOException ioe) {} + return false; + } + /** * Extract the file. Skips and ignores the signature and version. No verification. * diff --git a/core/java/src/net/i2p/util/ZipFileComment.java b/core/java/src/net/i2p/util/ZipFileComment.java new file mode 100644 index 000000000..9a5778768 --- /dev/null +++ b/core/java/src/net/i2p/util/ZipFileComment.java @@ -0,0 +1,111 @@ +package net.i2p.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.IOException; +import java.util.zip.ZipException; + +import net.i2p.data.DataHelper; + +/** + * Not available in ZipFile until Java 7. Refs: + * https://secure.wikimedia.org/wikipedia/en/wiki/ZIP_%28file_format%29 + * http://download.oracle.com/javase/1.5.0/docs/api/java/util/zip/ZipFile.html + * http://bugs.sun.com/view_bug.do?bug_id=6646605 + * + * Code modified from: + * http://www.flattermann.net/2009/01/read-a-zip-file-comment-with-java/ + * Beerware. + * + * since 0.8.8 + */ +public abstract class ZipFileComment { + + private static final int BLOCK_LEN = 22; + private static final byte[] magicStart = {0x50, 0x4b, 0x03, 0x04}; + private static final int HEADER_LEN = magicStart.length; + private static final byte[] magicDirEnd = {0x50, 0x4b, 0x05, 0x06}; + private static final int MAGIC_LEN = magicDirEnd.length; + + /** + * @param max The max length of the comment in bytes. + * If the actual comment is longer, it will not be found and + * this method will throw an IOE + * + * @return empty string if no comment, or the comment. + * The string is decoded with UTF-8 + * + * @throws IOE if no valid end-of-central-directory record found + */ + public static String getComment(File file, int max) throws IOException { + return getComment(file, max, 0); + } + + /** + * @param max The max length of the comment in bytes. + * If the actual comment is longer, it will not be found and + * this method will throw an IOE + * @param skip Number of bytes to skip in the file before looking for the + * zip header. Use 56 for sud/su2 files. + * + * @return empty string if no comment, or the comment. + * The string is decoded with UTF-8 + * + * @throws IOE if no valid end-of-central-directory record found + */ + public static String getComment(File file, int max, int skip) throws IOException { + if (!file.exists()) + throw new FileNotFoundException("File not found: " + file); + long len = file.length(); + if (len < BLOCK_LEN + HEADER_LEN + skip) + throw new ZipException("File too short: " + file); + if (len > Integer.MAX_VALUE) + throw new ZipException("File too long: " + file); + int fileLen = (int) len; + byte[] buffer = new byte[Math.min(fileLen - skip, max + BLOCK_LEN)]; + InputStream in = null; + try { + in = new FileInputStream(file); + if (skip > 0) + in.skip(skip); + byte[] hdr = new byte[HEADER_LEN]; + DataHelper.read(in, hdr); + if (!DataHelper.eq(hdr, magicStart)) + throw new ZipException("Not a zip file: " + file); + in.skip(fileLen - (skip + HEADER_LEN + buffer.length)); + DataHelper.read(in, buffer); + return getComment(buffer); + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + } + + /** + * go backwards from the end + */ + private static String getComment(byte[] buffer) throws IOException { + for (int i = buffer.length - (1 + BLOCK_LEN - MAGIC_LEN); i >= 0; i--) { + if (DataHelper.eq(buffer, i, magicDirEnd, 0, MAGIC_LEN)) { + int commentLen = (buffer[i + BLOCK_LEN - 2] & 0xff) + + ((buffer[i + BLOCK_LEN - 1] & 0xff) * 256); + return new String(buffer, i + BLOCK_LEN, commentLen, "UTF-8"); + } + } + throw new ZipException("No comment block found"); + } + + public static void main(String args[]) throws IOException { + if (args.length != 1) { + System.err.println("Usage: ZipFileComment file"); + return; + } + int skip = 0; + String file = args[0]; + if (file.endsWith(".sud") || file.endsWith(".su2")) + skip = 56; + String c = getComment(new File(file), 256, skip); + System.out.println("comment is: \"" + c + '\"'); + } +} From facbb8c9506d8ced1ce94a56f6220c2ab39ebe77 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 18 Jun 2011 16:33:47 +0000 Subject: [PATCH 2/5] * IRC Server: Send a message back if the tunnel is up but the server is down --- .../src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index 4537389bd..e21a5a4ab 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -63,6 +63,13 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p"; private static final long HEADER_TIMEOUT = 60*1000; + private final static byte[] ERR_UNAVAILABLE = + (":ircserver.i2p 499 you :" + + "This I2P IRC server is unvailable. It may be down or undergoing maintenance. " + + "Please try again later." + + "\r\n") + .getBytes(); + /** * @throws IllegalArgumentException if the I2PTunnel does not contain * valid config to contact the router @@ -125,7 +132,11 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { Socket s = new Socket(remoteHost, remotePort); new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null); } catch (SocketException ex) { - // TODO send the equivalent of a 503? + try { + // Send a response so the user doesn't just see a disconnect + // and blame his router or the network. + socket.getOutputStream().write(ERR_UNAVAILABLE); + } catch (IOException ioe) {} try { socket.close(); } catch (IOException ioe) {} From bda6d7819c8b6e9588400d9667fd460ca8cca8c7 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 23 Jun 2011 14:35:48 +0000 Subject: [PATCH 3/5] * BlockfileNamingService: - Support readonly blockfiles - Open blockfile readonly if not in router context - Log warning if blockfile is locked --- .../client/naming/BlockfileNamingService.java | 65 ++++++++++++++++--- core/java/src/net/metanotion/io/RAIFile.java | 15 ++++- .../metanotion/io/RandomAccessInterface.java | 7 ++ .../net/metanotion/io/block/BlockFile.java | 31 +++++++-- .../metanotion/io/block/index/BSkipList.java | 5 +- 5 files changed, 109 insertions(+), 14 deletions(-) diff --git a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java index ced910183..fdd035c2e 100644 --- a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java +++ b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java @@ -33,6 +33,7 @@ import net.i2p.data.Hash; import net.i2p.util.Log; import net.i2p.util.SecureFileOutputStream; +import net.metanotion.io.RAIFile; import net.metanotion.io.Serializer; import net.metanotion.io.block.BlockFile; import net.metanotion.io.data.UTF8StringBytes; @@ -76,10 +77,11 @@ import net.metanotion.util.skiplist.SkipList; public class BlockfileNamingService extends DummyNamingService { private final BlockFile _bf; - private final RandomAccessFile _raf; + private final RAIFile _raf; private final List _lists; private final List _invalid; private volatile boolean _isClosed; + private final boolean _readOnly; private static final Serializer _infoSerializer = new PropertiesSerializer(); private static final Serializer _stringSerializer = new UTF8StringBytes(); @@ -87,6 +89,7 @@ public class BlockfileNamingService extends DummyNamingService { private static final String HOSTS_DB = "hostsdb.blockfile"; private static final String FALLBACK_LIST = "hosts.txt"; + private static final String PROP_FORCE = "i2p.naming.blockfile.writeInAppContext"; private static final String INFO_SKIPLIST = "%%__INFO__%%"; private static final String PROP_INFO = "info"; @@ -100,6 +103,13 @@ public class BlockfileNamingService extends DummyNamingService { private static final String PROP_SOURCE = "s"; /** + * Opens the database at hostsdb.blockfile or creates a new + * one and imports entries from hosts.txt, userhosts.txt, and privatehosts.txt. + * + * If not in router context, the database will be opened read-only + * unless the property i2p.naming.blockfile.writeInAppContext is true. + * Not designed for simultaneous access by multiple processes. + * * @throws RuntimeException on fatal error */ public BlockfileNamingService(I2PAppContext context) { @@ -107,14 +117,31 @@ public class BlockfileNamingService extends DummyNamingService { _lists = new ArrayList(); _invalid = new ArrayList(); BlockFile bf = null; - RandomAccessFile raf = null; + RAIFile raf = null; + boolean readOnly = false; File f = new File(_context.getRouterDir(), HOSTS_DB); if (f.exists()) { try { // closing a BlockFile does not close the underlying file, // so we must create and retain a RAF so we may close it later - raf = new RandomAccessFile(f, "rw"); + + // *** Open readonly if not in router context (unless forced) + readOnly = (!f.canWrite()) || + ((!context.isRouterContext()) && (!context.getBooleanProperty(PROP_FORCE))); + raf = new RAIFile(f, true, !readOnly); bf = initExisting(raf); + if (readOnly && context.isRouterContext()) + _log.logAlways(Log.WARN, "Read-only hosts database in router context"); + if (bf.wasMounted()) { + if (context.isRouterContext()) + _log.logAlways(Log.WARN, "The hosts database was not closed cleanly or is still open by another process"); + else + _log.logAlways(Log.WARN, "The hosts database is possibly in use by another process, perhaps the router? " + + "The database is not designed for simultaneous access by multiple processes.\n" + + "If you are using clients outside the router JVM, consider using the hosts.txt " + + "naming service with " + + "i2p.naming.impl=net.i2p.client.naming.HostsTxtNamingService"); + } } catch (IOException ioe) { if (raf != null) { try { raf.close(); } catch (IOException e) {} @@ -131,7 +158,7 @@ public class BlockfileNamingService extends DummyNamingService { try { // closing a BlockFile does not close the underlying file, // so we must create and retain a RAF so we may close it later - raf = new RandomAccessFile(f, "rw"); + raf = new RAIFile(f, true, true); SecureFileOutputStream.setPerms(f); bf = init(raf); } catch (IOException ioe) { @@ -141,9 +168,11 @@ public class BlockfileNamingService extends DummyNamingService { _log.log(Log.CRIT, "Failed to initialize database", ioe); throw new RuntimeException(ioe); } + readOnly = false; } _bf = bf; _raf = raf; + _readOnly = readOnly; _context.addShutdownTask(new Shutdown()); } @@ -152,7 +181,7 @@ public class BlockfileNamingService extends DummyNamingService { * privatehosts.txt, userhosts.txt, and hosts.txt, * creating a skiplist in the database for each. */ - private BlockFile init(RandomAccessFile f) throws IOException { + private BlockFile init(RAIFile f) throws IOException { long start = _context.clock().now(); try { BlockFile rv = new BlockFile(f, true); @@ -221,7 +250,7 @@ public class BlockfileNamingService extends DummyNamingService { /** * Read the info block of an existing database. */ - private BlockFile initExisting(RandomAccessFile raf) throws IOException { + private BlockFile initExisting(RAIFile raf) throws IOException { long start = _context.clock().now(); try { BlockFile bf = new BlockFile(raf, false); @@ -427,6 +456,10 @@ public class BlockfileNamingService extends DummyNamingService { } private boolean put(String hostname, Destination d, Properties options, boolean checkExisting) { + if (_readOnly) { + _log.error("Add entry failed, read-only hosts database"); + return false; + } String key = hostname.toLowerCase(); String listname = FALLBACK_LIST; Properties props = new Properties(); @@ -475,6 +508,10 @@ public class BlockfileNamingService extends DummyNamingService { */ @Override public boolean remove(String hostname, Properties options) { + if (_readOnly) { + _log.error("Remove entry failed, read-only hosts database"); + return false; + } String key = hostname.toLowerCase(); String listname = FALLBACK_LIST; if (options != null) { @@ -657,7 +694,7 @@ public class BlockfileNamingService extends DummyNamingService { de != null && de.dest != null && de.dest.getPublicKey() != null; - if (!rv) + if ((!rv) && (!_readOnly)) _invalid.add(new InvalidEntry(key, listname)); return rv; } @@ -731,7 +768,11 @@ public class BlockfileNamingService extends DummyNamingService { try { _bf.close(); } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Error closing", ioe); } catch (RuntimeException e) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Error closing", e); } try { _raf.close(); @@ -870,8 +911,16 @@ public class BlockfileNamingService extends DummyNamingService { } } + /** + * BlockfileNamingService [force] + * force = force writable + */ public static void main(String[] args) { - BlockfileNamingService bns = new BlockfileNamingService(I2PAppContext.getGlobalContext()); + Properties ctxProps = new Properties(); + if (args.length > 0 && args[0].equals("force")) + ctxProps.setProperty(PROP_FORCE, "true"); + I2PAppContext ctx = new I2PAppContext(ctxProps); + BlockfileNamingService bns = new BlockfileNamingService(ctx); List names = null; Properties props = new Properties(); try { diff --git a/core/java/src/net/metanotion/io/RAIFile.java b/core/java/src/net/metanotion/io/RAIFile.java index 0988896e6..43ef2f2b8 100644 --- a/core/java/src/net/metanotion/io/RAIFile.java +++ b/core/java/src/net/metanotion/io/RAIFile.java @@ -38,13 +38,17 @@ import java.io.RandomAccessFile; public class RAIFile implements RandomAccessInterface, DataInput, DataOutput { private File f; private RandomAccessFile delegate; - private boolean r=false, w=false; + private final boolean r, w; public RAIFile(RandomAccessFile file) throws FileNotFoundException { this.f = null; this.delegate = file; + this.r = true; + // fake, we don't really know + this.w = true; } + /** @param read must be true */ public RAIFile(File file, boolean read, boolean write) throws FileNotFoundException { this.f = file; this.r = read; @@ -55,6 +59,15 @@ public class RAIFile implements RandomAccessInterface, DataInput, DataOutput { this.delegate = new RandomAccessFile(file, mode); } + /** + * I2P is the file writable? + * Only valid if the File constructor was used, not the RAF constructor + * @since 0.8.8 + */ + public boolean canWrite() { + return this.w; + } + public long getFilePointer() throws IOException { return delegate.getFilePointer(); } public long length() throws IOException { return delegate.length(); } public int read() throws IOException { return delegate.read(); } diff --git a/core/java/src/net/metanotion/io/RandomAccessInterface.java b/core/java/src/net/metanotion/io/RandomAccessInterface.java index 953d00698..fa382621c 100644 --- a/core/java/src/net/metanotion/io/RandomAccessInterface.java +++ b/core/java/src/net/metanotion/io/RandomAccessInterface.java @@ -39,6 +39,13 @@ public interface RandomAccessInterface { public void seek(long pos) throws IOException; public void setLength(long newLength) throws IOException; + /** + * I2P is the file writable? + * Only valid if the File constructor was used, not the RAF constructor + * @since 0.8.8 + */ + public boolean canWrite(); + // Closeable Methods public void close() throws IOException; diff --git a/core/java/src/net/metanotion/io/block/BlockFile.java b/core/java/src/net/metanotion/io/block/BlockFile.java index 74b8f723c..eee03a0da 100644 --- a/core/java/src/net/metanotion/io/block/BlockFile.java +++ b/core/java/src/net/metanotion/io/block/BlockFile.java @@ -90,6 +90,9 @@ public class BlockFile { private int mounted = 0; public int spanSize = 16; + /** I2P was the file locked when we opened it? */ + private final boolean _wasMounted; + private BSkipList metaIndex; /** cached list of free pages, only valid if freListStart > 0 */ private FreeListBlock flb; @@ -258,11 +261,19 @@ public class BlockFile { return curPage; } + /** Use this constructor with a readonly RAI for a readonly blockfile */ public BlockFile(RandomAccessInterface rai) throws IOException { this(rai, false); } + + /** RAF must be writable */ public BlockFile(RandomAccessFile raf) throws IOException { this(new RAIFile(raf), false); } + + /** RAF must be writable */ public BlockFile(RandomAccessFile raf, boolean init) throws IOException { this(new RAIFile(raf), init); } + + /** File must be writable */ public BlockFile(File f, boolean init) throws IOException { this(new RAIFile(f, true, true), init); } + /** Use this constructor with a readonly RAI and init = false for a readonly blockfile */ public BlockFile(RandomAccessInterface rai, boolean init) throws IOException { if(rai==null) { throw new NullPointerException(); } @@ -283,16 +294,26 @@ public class BlockFile { throw new IOException("Bad magic number"); } } - if (mounted != 0) + _wasMounted = mounted != 0; + if (_wasMounted) log.warn("Warning - file was not previously closed"); if(fileLen != file.length()) throw new IOException("Expected file length " + fileLen + " but actually " + file.length()); - mount(); + if (rai.canWrite()) + mount(); metaIndex = new BSkipList(spanSize, this, METAINDEX_PAGE, new StringBytes(), new IntBytes()); } + /** + * I2P was the file locked when we opened it? + * @since 0.8.8 + */ + public boolean wasMounted() { + return _wasMounted; + } + /** * Go to any page but the superblock. * Page 1 is the superblock, must use file.seek(0) to get there. @@ -454,8 +475,10 @@ public class BlockFile { } // Unmount. - file.seek(BlockFile.OFFSET_MOUNTED); - file.writeShort(0); + if (file.canWrite()) { + file.seek(BlockFile.OFFSET_MOUNTED); + file.writeShort(0); + } } public void bfck(boolean fix) { diff --git a/core/java/src/net/metanotion/io/block/index/BSkipList.java b/core/java/src/net/metanotion/io/block/index/BSkipList.java index c55b850d9..9313441f5 100644 --- a/core/java/src/net/metanotion/io/block/index/BSkipList.java +++ b/core/java/src/net/metanotion/io/block/index/BSkipList.java @@ -98,7 +98,8 @@ public class BSkipList extends SkipList { } if (BlockFile.log.shouldLog(Log.DEBUG)) BlockFile.log.debug("Loaded " + this + " cached " + levelHash.size() + " levels and " + spanHash.size() + " spans with " + total + " entries"); - if (levelCount != levelHash.size() || spans != spanHash.size() || size != total) { + if (bf.file.canWrite() && + (levelCount != levelHash.size() || spans != spanHash.size() || size != total)) { if (BlockFile.log.shouldLog(Log.WARN)) BlockFile.log.warn("On-disk counts were " + levelCount + " / " + spans + " / " + size + ", correcting"); size = total; @@ -117,6 +118,8 @@ public class BSkipList extends SkipList { @Override public void flush() { + if (!bf.file.canWrite()) + return; if (isClosed) { BlockFile.log.error("Already closed!! " + this, new Exception()); return; From 81dd267e2957fd91e0e7651f724f6e96614fd8e0 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 24 Jun 2011 17:41:23 +0000 Subject: [PATCH 4/5] throw IOE if uncompressed data too big, instead of silently truncating --- core/java/src/net/i2p/data/DataHelper.java | 33 ++++++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 3d6b36779..34210453e 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -1298,16 +1298,30 @@ public class DataHelper { private static final int MAX_UNCOMPRESSED = 40*1024; public static final int MAX_COMPRESSION = Deflater.BEST_COMPRESSION; public static final int NO_COMPRESSION = Deflater.NO_COMPRESSION; - /** compress the data and return a new GZIP compressed array */ + + /** + * Compress the data and return a new GZIP compressed byte array. + * @throws IllegalArgumentException if size is over 40KB + */ public static byte[] compress(byte orig[]) { return compress(orig, 0, orig.length); } + + /** + * Compress the data and return a new GZIP compressed byte array. + * @throws IllegalArgumentException if size is over 40KB + */ public static byte[] compress(byte orig[], int offset, int size) { return compress(orig, offset, size, MAX_COMPRESSION); } + + /** + * Compress the data and return a new GZIP compressed byte array. + * @throws IllegalArgumentException if size is over 40KB + */ public static byte[] compress(byte orig[], int offset, int size, int level) { if ((orig == null) || (orig.length <= 0)) return orig; - if (size >= MAX_UNCOMPRESSED) + if (size > MAX_UNCOMPRESSED) throw new IllegalArgumentException("tell jrandom size=" + size); ReusableGZIPOutputStream out = ReusableGZIPOutputStream.acquire(); out.setLevel(level); @@ -1335,10 +1349,18 @@ public class DataHelper { } - /** decompress the GZIP compressed data (returning null on error) */ + /** + * Decompress the GZIP compressed data (returning null on error). + * @throws IOE if uncompressed is over 40 KB + */ public static byte[] decompress(byte orig[]) throws IOException { return (orig != null ? decompress(orig, 0, orig.length) : null); } + + /** + * Decompress the GZIP compressed data (returning null on error). + * @throws IOE if uncompressed is over 40 KB + */ public static byte[] decompress(byte orig[], int offset, int length) throws IOException { if ((orig == null) || (orig.length <= 0)) return orig; @@ -1354,6 +1376,11 @@ public class DataHelper { if (read == -1) break; written += read; + if (written >= MAX_UNCOMPRESSED) { + if (in.available() > 0) + throw new IOException("Uncompressed data larger than " + MAX_UNCOMPRESSED); + break; + } } byte rv[] = new byte[written]; System.arraycopy(outBuf.getData(), 0, rv, 0, written); From 01b4b227ae2d58e2d09bb23f62ef7d0ec2217a3c Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 30 Jun 2011 12:21:43 +0000 Subject: [PATCH 5/5] small optimization --- core/java/src/net/i2p/client/naming/DummyNamingService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/java/src/net/i2p/client/naming/DummyNamingService.java b/core/java/src/net/i2p/client/naming/DummyNamingService.java index 2e009fe3f..228276da8 100644 --- a/core/java/src/net/i2p/client/naming/DummyNamingService.java +++ b/core/java/src/net/i2p/client/naming/DummyNamingService.java @@ -58,7 +58,8 @@ class DummyNamingService extends NamingService { if (hostname.length() >= 516) { d = lookupBase64(hostname); // What the heck, cache these too - putCache(hostname, d); + if (d != null) + putCache(hostname, d); return d; }