{"record":{"id":"6492dec91e464a43","repo":"apache/hadoop","slug":"class-name-comparators-are-not-enabled-set-tfile","errorCode":null,"errorMessage":"Class-name comparators are not enabled (set tfile.comparator.jclass.enabled=true to allow): {comparator}","messagePattern":"Class-name comparators are not enabled \\(set tfile\\.comparator\\.jclass\\.enabled=true to allow\\): (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/file/tfile/TFile.java","lineNumber":2100,"sourceCode":"\n    static BytesComparator makeComparator(String comparator) {\n      return makeComparator(comparator, new Configuration());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    static BytesComparator makeComparator(String comparator,\n        Configuration conf) {\n      if (comparator.length() == 0) {\n        // unsorted keys\n        return null;\n      }\n      if (comparator.equals(COMPARATOR_MEMCMP)) {\n        // default comparator\n        return new BytesComparator(new MemcmpRawComparator());\n      } else if (comparator.startsWith(COMPARATOR_JCLASS)) {\n        if (!conf.getBoolean(TFILE_COMPARATOR_JCLASS_ENABLED,\n            TFILE_COMPARATOR_JCLASS_ENABLED_DEFAULT)) {\n          throw new IllegalArgumentException(\n              \"Class-name comparators are not enabled (set \"\n                  + TFILE_COMPARATOR_JCLASS_ENABLED + \"=true to allow): \"\n                  + comparator);\n        }\n        String compClassName =\n            comparator.substring(COMPARATOR_JCLASS.length()).trim();\n        try {\n          // Resolve without running the class initializer, confirm it really\n          // is a RawComparator, and only then load and construct it.\n          Class<?> compClass =\n              Class.forName(compClassName, false, conf.getClassLoader());\n          RawComparator<Object> rawComparator =\n              (RawComparator<Object>) compClass.asSubclass(RawComparator.class)\n                  .getDeclaredConstructor().newInstance();\n          return new BytesComparator(rawComparator);\n        } catch (Exception e) {\n          throw new IllegalArgumentException(\n              \"Failed to instantiate comparator: \" + comparator + \"(\"","sourceCodeStart":2082,"sourceCodeEnd":2118,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/file/tfile/TFile.java#L2082-L2118","documentation":"IllegalArgumentException from TFileMeta.makeComparator when the comparator name stored in the file starts with \"jclass:\" but the Configuration does not set tfile.comparator.jclass.enabled (default false). The comparator name comes from untrusted file metadata; instantiating an arbitrary class from it is a class-loading gadget, so the reflective path is opt-in. This guard only fires on files that declare a Java-class comparator.","triggerScenarios":"Opening a TFile written with a 'jclass:com.foo.MyComparator' comparator while the reader's Configuration leaves tfile.comparator.jclass.enabled unset/false. Both the Writer constructor and the Reader path run makeComparator, so writing such a file needs the same flag.","commonSituations":"Default-hardened clusters reading older pre-hardening files that embedded jclass comparators; jobs where the writer node had the flag set but the reader job's configuration does not; security review forcing the flag off and legacy files failing immediately after.","solutions":["Only if you trust the file's origin: conf.setBoolean(\"tfile.comparator.jclass.enabled\", true) on the exact Configuration handed to TFile.Reader/Writer.","Prefer rewriting the data with the standard \"memcmp\" comparator so no reflective loading is ever needed.","If jclass support is required fleet-wide, set the key in the job/cluster configuration and restrict who can write TFiles, since the flag re-opens a remote classloading vector."],"exampleFix":"// before\nConfiguration conf = new Configuration();\nTFile.Reader r = new TFile.Reader(in, len, conf); // IAE: jclass comparators disabled\n// after\nConfiguration conf = new Configuration();\nconf.setBoolean(TFile.TFILE_COMPARATOR_JCLASS_ENABLED, true); // trusted files only\nTFile.Reader r = new TFile.Reader(in, len, conf);","handlingStrategy":"validation","validationCode":"Configuration conf = new Configuration();\n// only for files from a trusted producer that declares a jclass comparator\nif (isTrustedProducer(path) && needsJClassComparator(path)) {\n  conf.setBoolean(TFile.TFILE_COMPARATOR_JCLASS_ENABLED, true);\n}\nTFile.Reader r = new TFile.Reader(in, len, conf);","typeGuard":null,"tryCatchPattern":"try {\n  TFile.Reader r = new TFile.Reader(in, len, conf);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"tfile.comparator.jclass.enabled\")) { /* set flag if trusted, else rewrite file */ }\n  else throw e;\n}","preventionTips":["Prefer memcmp TFiles; use jclass comparators only where ordering logic demands it.","Set the flag in job config, not ad-hoc code, so its security impact is auditable.","Remember the flag is read from the Configuration passed to the Reader/Writer constructor."],"tags":["tfile","hadoop-common","comparator","security","configuration","class-loading"],"backgroundTag":"insecure-deserialization-blocked","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}