This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new b401c34a4 StrBuilder.readFrom(Readable), exposes stale internal buffer 
to Readable parameter (#1652)
b401c34a4 is described below

commit b401c34a438f0dd7ec1e98939fb68232af86a448
Author: Gary Gregory <[email protected]>
AuthorDate: Sun May 17 16:38:18 2026 -0400

    StrBuilder.readFrom(Readable), exposes stale internal buffer to Readable 
parameter (#1652)
---
 .../org/apache/commons/lang3/text/StrBuilder.java  |   5 +-
 .../commons/lang3/text/StrBuilderClearTest.java    | 107 +++++++++++++++++++++
 2 files changed, 108 insertions(+), 4 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/text/StrBuilder.java 
b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java
index 70fbd1e7b..7bb7a97d4 100644
--- a/src/main/java/org/apache/commons/lang3/text/StrBuilder.java
+++ b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java
@@ -1609,15 +1609,12 @@ public char charAt(final int index) {
      * This method does not reduce the size of the internal character buffer.
      * To do that, call {@code clear()} followed by {@link 
#minimizeCapacity()}.
      * </p>
-     * <p>
-     * This method is the same as {@link #setLength(int)} called with zero
-     * and is provided to match the API of Collections.
-     * </p>
      *
      * @return {@code this} instance.
      */
     public StrBuilder clear() {
         size = 0;
+        Arrays.fill(buffer, '0');
         return this;
     }
 
diff --git 
a/src/test/java/org/apache/commons/lang3/text/StrBuilderClearTest.java 
b/src/test/java/org/apache/commons/lang3/text/StrBuilderClearTest.java
new file mode 100644
index 000000000..8953b95d8
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/text/StrBuilderClearTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.text;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link StrBuilder#clear()} zero-copy APIs expose backing char buffer.
+ * <p>
+ * readFrom(Readable) Reader branch: reads directly into the internal char[] 
buffer, so a Reader that is also an attacker can observe stale chars in that 
buffer
+ * beyond the logical content.
+ * </p>
+ *
+ * <p>
+ * Pre-patch: A Reader can inspect chars beyond the current write position.
+ * </p>
+ * <p>
+ * Post-patch: readFrom uses a temporary buffer so stale internal chars are 
not exposed.
+ * </p>
+ */
+@SuppressWarnings("deprecation")
+public class StrBuilderClearTest {
+
+    /**
+     * A Reader that, upon reading, inspects the char array it has been given 
access to (positions beyond offset+len that may contain stale data), records 
them,
+     * then supplies its normal data.
+     */
+    static class SpyReader extends Reader {
+
+        private boolean done;
+        private char[] observedExtra;
+        private final char[] supply;
+
+        SpyReader(final String supply) {
+            this.supply = supply.toCharArray();
+        }
+
+        @Override
+        public void close() {
+            // empty
+        }
+
+        boolean observedStaleChars(final String marker) {
+            if (observedExtra == null) {
+                return false;
+            }
+            return new String(observedExtra).contains(marker);
+        }
+
+        @Override
+        public int read(final char[] cbuf, final int off, final int len) {
+            if (done) {
+                return -1;
+            }
+            done = true;
+            // Record chars in the buffer beyond where we will write
+            final int toWrite = Math.min(supply.length, len);
+            final int staleStart = off + toWrite;
+            final int staleLen = cbuf.length - staleStart;
+            if (staleLen > 0) {
+                observedExtra = new char[staleLen];
+                System.arraycopy(cbuf, staleStart, observedExtra, 0, staleLen);
+            }
+            System.arraycopy(supply, 0, cbuf, off, toWrite);
+            return toWrite;
+        }
+    }
+
+    @Test
+    public void testReadFromReaderDoesNotExposeStaleInternalBuffer() throws 
IOException {
+        final StrBuilder sb = new StrBuilder();
+        // Write a long "secret" string to fill the internal buffer
+        sb.append("SECRET_DATA_SHOULD_NOT_LEAK_ABCDEFGHIJ");
+        // Now clear it (logical size goes to 0, but internal buffer still 
holds old chars)
+        sb.clear();
+        // sb now has logical size 0 but internal buffer still contains 
"SECRET_DATA..."
+        // Now readFrom a SpyReader that only supplies short data but inspects 
the buffer.
+        // The spy observes what's in the buffer BEYOND the bytes it writes.
+        // We write "hi" (2 chars), so stale content starts at position 2:
+        // "CRET_DATA_SHOULD_NOT_LEAK_ABCDEFGHIJ..." is at positions 2+.
+        try (SpyReader spy = new SpyReader("hi")) {
+            sb.readFrom(spy);
+            // Post-patch: stale chars must NOT be visible to the Reader
+            assertFalse(spy.observedStaleChars("_DATA_SHOULD_NOT_LEAK"));
+        }
+    }
+}

Reply via email to