@@ -52,6 +52,7 @@ public final class BinaryImporter implements JmeImporter {
5252 .getName ());
5353
5454 private AssetManager assetManager ;
55+ private SavableClassFilter classFilter = SavableClassFilter .ACCEPT_ALL ;
5556
5657 //Key - alias, object - bco
5758 private final HashMap <String , BinaryClassObject > classes
@@ -99,6 +100,27 @@ public AssetManager getAssetManager(){
99100 return assetManager ;
100101 }
101102
103+ /**
104+ * Sets the policy used before this importer instantiates classes named by
105+ * a J3O file. The default accepts all classes for backward compatibility.
106+ * Use a restrictive filter when loading assets from untrusted sources.
107+ *
108+ * @param classFilter the filter to apply
109+ */
110+ public void setClassFilter (SavableClassFilter classFilter ) {
111+ if (classFilter == null ) {
112+ throw new NullPointerException ("classFilter" );
113+ }
114+ this .classFilter = classFilter ;
115+ }
116+
117+ /**
118+ * @return the current class filter
119+ */
120+ public SavableClassFilter getClassFilter () {
121+ return classFilter ;
122+ }
123+
102124 @ Override
103125 public Object load (AssetInfo info ){
104126// if (!(info.getKey() instanceof ModelKey))
@@ -145,6 +167,9 @@ public Savable load(InputStream is, ReadListener listener, ByteArrayOutputStream
145167 formatVersion = ByteUtils .readInt (bis );
146168 numClasses = ByteUtils .readInt (bis );
147169
170+ if (formatVersion < 0 ) {
171+ throw new IOException ("Invalid J3O format version: " + formatVersion );
172+ }
148173 // check if this binary is from the future
149174 if (formatVersion > FormatVersion .VERSION ){
150175 throw new IOException ("The binary file is of newer version than expected! " +
@@ -161,6 +186,9 @@ public Savable load(InputStream is, ReadListener listener, ByteArrayOutputStream
161186 }
162187
163188 int bytes = 4 ;
189+ if (numClasses <= 0 ) {
190+ throw new IOException ("Invalid J3O class count: " + numClasses );
191+ }
164192 aliasWidth = ((int )FastMath .log (numClasses , 256 ) + 1 );
165193
166194 classes .clear ();
@@ -170,7 +198,7 @@ public Savable load(InputStream is, ReadListener listener, ByteArrayOutputStream
170198 // jME3 NEW: Read class version number
171199 int [] classHierarchyVersions ;
172200 if (formatVersion >= 1 ){
173- int classHierarchySize = bis . read ( );
201+ int classHierarchySize = readUnsignedByte ( bis , "class hierarchy size" );
174202 classHierarchyVersions = new int [classHierarchySize ];
175203 for (int j = 0 ; j < classHierarchySize ; j ++){
176204 classHierarchyVersions [j ] = ByteUtils .readInt (bis );
@@ -181,23 +209,29 @@ public Savable load(InputStream is, ReadListener listener, ByteArrayOutputStream
181209
182210 // read classname and classname size
183211 int classLength = ByteUtils .readInt (bis );
212+ checkLength (classLength );
184213 String className = readString (bis , classLength );
214+ if (!classFilter .isAllowed (SavableClassUtil .remapClass (className ))) {
215+ throw new IOException ("J3O class rejected by filter: " + className );
216+ }
185217
186218 BinaryClassObject bco = new BinaryClassObject ();
187219 bco .alias = alias .getBytes ();
188220 bco .className = className ;
189221 bco .classHierarchyVersions = classHierarchyVersions ;
190222
191223 int fields = ByteUtils .readInt (bis );
224+ checkLength (fields );
192225 bytes += (8 + aliasWidth + classLength );
193226
194227 bco .nameFields = new HashMap <String , BinaryClassField >(fields );
195228 bco .aliasFields = new HashMap <Byte , BinaryClassField >(fields );
196229 for (int x = 0 ; x < fields ; x ++) {
197- byte fieldAlias = (byte )bis . read ( );
198- byte fieldType = (byte )bis . read ( );
230+ byte fieldAlias = (byte ) readUnsignedByte ( bis , "field alias" );
231+ byte fieldType = (byte ) readUnsignedByte ( bis , "field type" );
199232
200233 int fieldNameLength = ByteUtils .readInt (bis );
234+ checkLength (fieldNameLength );
201235 String fieldName = readString (bis , fieldNameLength );
202236 BinaryClassField bcf = new BinaryClassField (fieldName , fieldAlias , fieldType );
203237 bco .nameFields .put (fieldName , bcf );
@@ -209,13 +243,17 @@ public Savable load(InputStream is, ReadListener listener, ByteArrayOutputStream
209243 if (listener != null ) listener .readBytes (bytes );
210244
211245 int numLocs = ByteUtils .readInt (bis );
246+ checkLength (numLocs );
212247 bytes = 4 ;
213248
214249 capsuleTable .clear ();
215250 locationTable .clear ();
216251 for (int i = 0 ; i < numLocs ; i ++) {
217252 int id = ByteUtils .readInt (bis );
218253 int loc = ByteUtils .readInt (bis );
254+ if (loc < 0 ) {
255+ throw new IOException ("Invalid negative J3O object location: " + loc );
256+ }
219257 locationTable .put (id , loc );
220258 bytes += 8 ;
221259 }
@@ -290,15 +328,20 @@ public InputCapsule getCapsule(Savable id) {
290328 }
291329
292330 protected String readString (InputStream f , int length ) throws IOException {
331+ checkLength (length );
293332 byte [] data = new byte [length ];
294333 for (int j = 0 ; j < length ; j ++) {
295- data [j ] = (byte )f . read ( );
334+ data [j ] = (byte ) readUnsignedByte ( f , "string" );
296335 }
297336
298337 return new String (data );
299338 }
300339
301340 protected String readString (int length , int offset ) throws IOException {
341+ checkLength (length );
342+ if (offset < 0 || offset + length < offset || offset + length > dataArray .length ) {
343+ throw new IOException ("String outside J3O payload: offset=" + offset + ", length=" + length );
344+ }
302345 byte [] data = new byte [length ];
303346 for (int j = 0 ; j < length ; j ++) {
304347 data [j ] = dataArray [j +offset ];
@@ -307,14 +350,35 @@ protected String readString(int length, int offset) throws IOException {
307350 return new String (data );
308351 }
309352
353+ private void checkLength (int length ) throws IOException {
354+ if (length < 0 ) {
355+ throw new IOException ("Invalid negative J3O length/count: " + length );
356+ }
357+ }
358+
359+ private int readUnsignedByte (InputStream input , String label ) throws IOException {
360+ int value = input .read ();
361+ if (value < 0 ) {
362+ throw new EOFException ("Unexpected end of J3O while reading " + label );
363+ }
364+ return value ;
365+ }
366+
310367 public Savable readObject (int id ) {
311368
312369 if (contentTable .get (id ) != null ) {
313370 return contentTable .get (id );
314371 }
315372
316373 try {
317- int loc = locationTable .get (id );
374+ Integer objectLocation = locationTable .get (id );
375+ if (objectLocation == null ) {
376+ throw new IOException ("Missing J3O object location for id: " + id );
377+ }
378+ int loc = objectLocation ;
379+ if (loc < 0 || loc >= dataArray .length ) {
380+ throw new IOException ("J3O object location outside payload: " + loc );
381+ }
318382
319383 String alias = readString (aliasWidth , loc );
320384 loc +=aliasWidth ;
@@ -326,10 +390,16 @@ public Savable readObject(int id) {
326390 return null ;
327391 }
328392
393+ if (loc + 4 > dataArray .length ) {
394+ throw new IOException ("Truncated J3O object length at payload offset: " + loc );
395+ }
329396 int dataLength = ByteUtils .convertIntFromBytes (dataArray , loc );
330397 loc +=4 ;
398+ if (dataLength < 0 || loc + dataLength < loc || loc + dataLength > dataArray .length ) {
399+ throw new IOException ("Invalid J3O object data length: " + dataLength );
400+ }
331401
332- Savable out = SavableClassUtil .fromName (bco .className );
402+ Savable out = SavableClassUtil .fromName (bco .className , classFilter );
333403
334404 BinaryInputCapsule cap = new BinaryInputCapsule (this , out , bco );
335405 cap .setContent (dataArray , loc , loc +dataLength );
@@ -348,4 +418,4 @@ public Savable readObject(int id) {
348418 return null ;
349419 }
350420 }
351- }
421+ }
0 commit comments