github-actions[bot] commented on code in PR #69527:
URL: https://github.com/apache/doris/pull/69527#discussion_r4369701678


##########
fe/be-java-extensions/hadoop-deps/src/main/java/org/apache/hadoop/fs/FileSystem.java:
##########
@@ -3622,13 +3633,670 @@ public static Class<? extends FileSystem> 
getFileSystemClass(String scheme,
    * @return the initialized filesystem.
    * @throws IOException problems loading or initializing the FileSystem
    */
+  // DORIS-PATCH: TVF policy is checked against the frozen configuration used 
by the consumer.
+  public static int getDorisTvfGuardVersion() {
+    return 1;
+  }
+
+  public static final class TvfGuard {
+    public static final String POLICY_KEY = "fs.doris.tvf.remote.paths";
+    public static final String NATIVE_POLICY = "native";
+    public static final String JAVA_READ_POLICY = "java-read";
+    public static final String ROOT_KEY = "fs.doris.tvf.root";
+    public static final String PATH_KEY = "fs.doris.tvf.path";
+    public static final String CONTEXT_KEY = "fs.doris.tvf.context";
+    private static final ThreadLocal<Context> LAST_NATIVE_CONTEXT = new 
ThreadLocal<>();
+
+    private TvfGuard() {
+    }
+
+    public static final class Rule {
+      private final String filesystem;
+      private final String path;
+      private final boolean descendants;
+
+      public Rule(String filesystem, String path, boolean descendants) {
+        this.filesystem = filesystem;
+        this.path = path;
+        this.descendants = descendants;
+      }
+    }
+
+    public static String encode(List<Rule> rules) {
+      if (FileSystem.getDorisTvfGuardVersion() != 1) {
+        throw new IllegalStateException("TVF Hadoop runtime policy version 
mismatch");
+      }
+      try {
+        java.io.ByteArrayOutputStream bytes = new 
java.io.ByteArrayOutputStream();
+        java.io.DataOutputStream out = new java.io.DataOutputStream(bytes);
+        out.writeInt(1);
+        out.writeInt(rules.size());
+        for (Rule rule : rules) {
+          out.writeUTF(rule.filesystem);
+          out.writeUTF(rule.path);
+          out.writeBoolean(rule.descendants);
+        }
+        out.flush();
+        return 
java.util.Base64.getEncoder().encodeToString(bytes.toByteArray());
+      } catch (IOException e) {
+        throw new IllegalArgumentException("Cannot encode TVF Hadoop route 
policy", e);
+      }
+    }
+
+    /** Routes touched by Globber, without constructing a filesystem or 
listing data. */
+    public static List<Rule> globRules(URI filesystem, String pattern) throws 
IOException {
+      List<Rule> rules = new ArrayList<>();
+      rules.add(new Rule(filesystem.toString(), pattern, true));
+      if (pattern.indexOf('\\') < 0) {
+        return rules;
+      }
+      for (String expanded : GlobExpander.expand(pattern)) {
+        Path flattened = new Path(expanded.isEmpty() ? Path.CUR_DIR : 
expanded);
+        String path = flattened.toUri().getPath();
+        Path candidate = new Path(flattened.isAbsolute() ? "/" : Path.CUR_DIR);
+        for (String component : path.split("/")) {
+          if (component.isEmpty()) {
+            continue;
+          }
+          GlobFilter filter = new GlobFilter(component);
+          if (filter.hasPattern()) {
+            // Globber lists this parent before it knows the matching child 
names. Keep
+            // prior wildcard components as a conservative subtree route, 
including when
+            // a later escaped '..' moves the final candidate outside that 
subtree.
+            rules.add(new Rule(filesystem.toString(), new Path(candidate, 
"*").toUri().getPath(), true));
+          }
+          component = component.replaceAll("\\\\(.)", "$1");
+          candidate = new Path(candidate, component);
+        }
+        rules.add(new Rule(filesystem.toString(), candidate.toUri().getPath(), 
true));
+      }
+      return rules;
+    }
+
+    private static IOException rejected() {
+      return new IOException("Hadoop TVF requires a proven remote filesystem 
route. "
+          + "Use local() with LOCAL TVF privileges and secure-path 
restrictions.");
+    }
+
+    private static Configuration freeze(Configuration conf) throws IOException 
{
+      // Service providers (notably HDFS) register their default/site 
resources on first load.
+      // Load them before copying raw values, as an ordinary FileSystem lookup 
does.
+      if (!FILE_SYSTEMS_LOADED) {
+        loadFileSystems();
+      }
+      if (conf instanceof FrozenConfiguration) {
+        return conf;
+      }
+      return new FrozenConfiguration(conf);
+    }
+
+    private static Configuration prepare(Configuration conf) throws 
IOException {
+      String policy = conf.getRaw(POLICY_KEY);
+      if (policy == null || policy.isEmpty()) {
+        return conf;
+      }
+      Configuration frozen = freeze(conf);
+      Context context = ((FrozenConfiguration) frozen).context;
+      if (NATIVE_POLICY.equals(policy)) {
+        context.checkNative(frozen.getRaw(ROOT_KEY), frozen.getRaw(PATH_KEY));
+      } else if (JAVA_READ_POLICY.equals(policy)) {
+        context.checkJava(new Path(frozen.getRaw(PATH_KEY)));
+      } else {
+        context.checkRules(policy);
+      }
+      return frozen;
+    }
+
+    public static void check(URI ignored, Configuration conf) throws 
IOException {
+      prepare(conf);
+    }
+
+    public static void clearNativeContext() {
+      LAST_NATIVE_CONTEXT.remove();
+    }
+
+    public static Context takeNativeContext() {
+      Context result = LAST_NATIVE_CONTEXT.get();
+      LAST_NATIVE_CONTEXT.remove();
+      return result;
+    }
+
+    public static Configuration prepareJavaRead(Configuration conf, Path path, 
String marker) throws IOException {
+      if (marker == null || marker.isEmpty()) {
+        return conf;
+      }
+      if (!NATIVE_POLICY.equals(marker)) {
+        throw rejected();
+      }
+      Configuration request = new Configuration(conf);
+      request.set(POLICY_KEY, JAVA_READ_POLICY);
+      request.set(ROOT_KEY, path.toUri().toString());
+      request.set(PATH_KEY, path.toString());
+      return prepare(request);
+    }
+
+    public static void checkJavaRead(FileSystem fs, Path path, String marker) 
throws IOException {
+      if (marker == null || marker.isEmpty()) {
+        return;
+      }
+      if (!NATIVE_POLICY.equals(marker)) {
+        throw rejected();
+      }
+      // The marker is request-owned, never inferred from an unmarked cached 
filesystem.
+      Configuration conf = fs.getConf();
+      if (JAVA_READ_POLICY.equals(conf.getRaw(POLICY_KEY))) {
+        // S3A derives a Configuration for bucket options. Its copy preserves 
our internal
+        // marker and raw snapshot values, but not the private Configuration 
subclass.
+        ((FrozenConfiguration) freeze(conf)).context.checkJava(path);
+      } else {
+        throw rejected();
+      }
+    }
+
+    private static final class FrozenConfiguration extends Configuration {
+      private final Context context;
+
+      private FrozenConfiguration(Configuration source) throws IOException {
+        super(false);
+        setClassLoader(source.getClassLoader());
+        java.util.SortedMap<String, String> values = new java.util.TreeMap<>();
+        for (Map.Entry<String, String> entry : source) {
+          String key = entry.getKey();
+          String value = source.getRaw(key);
+          set(key, value);
+          if (!PATH_KEY.equals(key) && !ROOT_KEY.equals(key) && 
!CONTEXT_KEY.equals(key)) {
+            values.put(key, value);
+          }
+        }
+        // ViewFS's private InnerCache folds authority case (A/a). Guarded 
children must
+        // instead use FileSystem's exact-authority/profile cache below.
+        set("fs.viewfs.enable.inner.cache", "false");
+        values.put("fs.viewfs.enable.inner.cache", "false");
+        try {
+          java.security.MessageDigest digest = 
java.security.MessageDigest.getInstance("SHA-256");
+          java.io.DataOutputStream output = new java.io.DataOutputStream(
+              new java.security.DigestOutputStream(new java.io.OutputStream() {
+                @Override
+                public void write(int value) {
+                }
+
+                @Override
+                public void write(byte[] values, int offset, int length) {
+                }
+              }, digest));
+          for (Map.Entry<String, String> entry : values.entrySet()) {
+            for (String value : new String[] {entry.getKey(), 
entry.getValue()}) {
+              // Preserve raw Java code units, including unpaired surrogates. 
UTF-8 replacement
+              // would give a literal '?' and an unpaired surrogate the same 
cache profile.
+              output.writeInt(value.length());
+              output.writeChars(value);
+            }
+          }
+          output.flush();
+          StringBuilder identity = new StringBuilder();
+          for (byte value : digest.digest()) {
+            identity.append(Character.forDigit((value & 255) >>> 4, 16));
+            identity.append(Character.forDigit(value & 15, 16));
+          }
+          set(CONTEXT_KEY, identity.toString());
+        } catch (java.security.NoSuchAlgorithmException e) {
+          throw new IOException("SHA-256 is required for TVF Hadoop cache 
isolation");
+        }
+        context = new Context(this);
+      }
+    }
+
+    public static final class Context {
+      private final Configuration conf;
+      private final Map<String, String> routingKeys = new HashMap<>();
+
+      private Context(Configuration conf) {
+        this.conf = conf;
+        for (Map.Entry<String, String> entry : conf) {
+          String key = entry.getKey();
+          if ("fs.defaultFS".equals(key) || "fs.default.name".equals(key)
+              || key.startsWith("fs.viewfs.") || (key.startsWith("fs.") && 
key.endsWith(".impl"))) {
+            routingKeys.put(key, entry.getValue());
+          }
+        }
+      }
+
+      private EffectiveRoute route(boolean descendants) {
+        return new EffectiveRoute(routingKeys, descendants, conf);
+      }
+
+      public String profile() {
+        return conf.getRaw(CONTEXT_KEY);
+      }
+
+      public boolean isDirectRemote() throws IOException {
+        String root = conf.getRaw(ROOT_KEY);
+        if (root == null) {
+          throw rejected();
+        }
+        try {
+          URI uri = nativeUri(root);
+          return route(false).provider(uri.getScheme()) == 
Route.Provider.REMOTE;
+        } catch (RuntimeException | URISyntaxException e) {
+          throw rejected();
+        }
+      }
+
+      private static URI nativeUri(String root) throws URISyntaxException {
+        root = root.trim();
+        if (root.isEmpty() || "default".equals(root)) {
+          throw new URISyntaxException("", "Unproven native filesystem");
+        }
+        return new URI(root.contains("://") ? root : "hdfs://" + root);
+      }
+
+      public void checkNative(String root, String path) throws IOException {
+        if (root == null || path == null || path.isEmpty()) {
+          throw rejected();
+        }
+        try {
+          URI uri = nativeUri(root);
+          String actual = new Path(path).toUri().getPath();
+          route(false).checkRoute(uri, actual, new HashSet<>());

Review Comment:
   [P1] Validate the native write parent in the runtime configuration
   
   `checkNative` validates the whole filename for both reads and writes, but 
the pinned 
[ViewFileSystem.create](https://github.com/apache/doris-thirdparty/blob/hadoop-3.4.2.2-for-doris/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java#L471-L483)
 resolves it with `resolveLastComponent=false`. With a local parent mount 
inherited only from BE core-site.xml and a remote mount on the final component, 
this check approves the remote child while creation dispatches through the 
local parent. A NUL-terminated file_path prefix makes that final component 
stable: SQL preserves NUL, VTVFTableWriter appends its suffix, and hdfsOpenFile 
truncates it via path.c_str(). A global LOAD caller can thus create or 
overwrite a BE-local file while enable_local_tvf=false. The existing FE parent 
check cannot see the BE-only mount; this is a separate defect in the newly 
added runtime guard. Preserve read/write intent in the native policy and 
validate the actual cre
 ate-parent route against the frozen runtime configuration before construction 
and cached reuse; add a regression for differing parent/leaf mounts.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to