1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.lang3.text;
19
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertTrue;
23
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.io.ObjectInputStream;
27 import java.io.Reader;
28 import java.nio.charset.StandardCharsets;
29
30 import org.apache.commons.lang3.CharUtils;
31 import org.apache.commons.lang3.SerializationUtils;
32 import org.junit.jupiter.api.Test;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 @SuppressWarnings("deprecation")
49 public class StrBuilderClearTest {
50
51
52
53
54
55 static class SpyReader extends Reader {
56
57 private boolean done;
58 private char[] observedExtra;
59 private final char[] supply;
60
61 SpyReader(final String supply) {
62 this.supply = supply.toCharArray();
63 }
64
65 @Override
66 public void close() {
67
68 }
69
70 boolean observedStaleChars(final String marker) {
71 if (observedExtra == null) {
72 return false;
73 }
74 return new String(observedExtra).contains(marker);
75 }
76
77 @Override
78 public int read(final char[] cbuf, final int off, final int len) {
79 if (done) {
80 return -1;
81 }
82 done = true;
83
84 final int toWrite = Math.min(supply.length, len);
85 final int staleStart = off + toWrite;
86 final int staleLen = cbuf.length - staleStart;
87 if (staleLen > 0) {
88 observedExtra = new char[staleLen];
89 System.arraycopy(cbuf, staleStart, observedExtra, 0, staleLen);
90 }
91 System.arraycopy(supply, 0, cbuf, off, toWrite);
92 return toWrite;
93 }
94 }
95
96
97 private static boolean containsUtf16Be(final byte[] haystack, final String needle) throws IOException {
98 final byte[] needleBytes = needle.getBytes(StandardCharsets.UTF_16BE);
99 outer: for (int i = 0; i <= haystack.length - needleBytes.length; i++) {
100 for (int j = 0; j < needleBytes.length; j++) {
101 if (haystack[i + j] != needleBytes[j]) {
102 continue outer;
103 }
104 }
105 return true;
106 }
107 return false;
108 }
109
110 @Test
111 public void testDeserializedStrBuilderHasNoStaleBufferContent() throws Exception {
112 final StrBuilder sb = new StrBuilder("secret_password_xyzzy");
113 sb.clear();
114 sb.append("safe");
115 final byte[] serialized = SerializationUtils.serialize(sb);
116 final StrBuilder sb2;
117
118 try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized))) {
119 sb2 = (StrBuilder) ois.readObject();
120 }
121 final char[] buf2 = sb2.buffer;
122 final String bufContent = new String(buf2);
123 assertFalse(bufContent.contains("secret_password"), "Deserialized StrBuilder buffer must not contain stale chars: " + bufContent);
124 }
125
126 @Test
127 public void testReadFromReaderDoesNotExposeStaleInternalBuffer() throws IOException {
128 final StrBuilder sb = new StrBuilder();
129
130 sb.append("SECRET_DATA_SHOULD_NOT_LEAK_ABCDEFGHIJ");
131
132 sb.clear();
133
134
135
136
137
138 try (SpyReader spy = new SpyReader("hi")) {
139 sb.readFrom(spy);
140
141 assertFalse(spy.observedStaleChars("_DATA_SHOULD_NOT_LEAK"));
142 }
143 }
144
145 @Test
146 void testReplaceImplLeavesResidue() throws Exception {
147 final String string = "SECRET_PASSWORD_DATA";
148 final StrBuilder sb = new StrBuilder(string);
149 assertEquals(20, sb.length());
150
151 sb.replace(0, 20, "X");
152 assertEquals(1, sb.length());
153 assertEquals("X", sb.toString());
154 final char[] buf = sb.getBuffer();
155 assertTrue(buf.length >= 20);
156
157
158
159
160
161 assertEquals(CharUtils.NUL, buf[5]);
162
163 for (int i = 1; i < 20; i++) {
164 assertEquals(CharUtils.NUL, buf[i]);
165 }
166 }
167
168 @Test
169 void testSetLengthShrinkLeavesResidue() throws Exception {
170 final String string = "CONFIDENTIAL_TOKEN_VALUE";
171 final int len = string.length();
172 final StrBuilder sb = new StrBuilder(string);
173 assertEquals(len, sb.length());
174
175 sb.setLength(5);
176 assertEquals(5, sb.length());
177 assertEquals("CONFI", sb.toString());
178 final char[] buf = sb.getBuffer();
179 assertTrue(buf.length >= len);
180
181 assertEquals(CharUtils.NUL, buf[10]);
182 for (int i = 5; i < len; i++) {
183 assertEquals(CharUtils.NUL, buf[i]);
184 }
185 }
186
187 @Test
188 public void testStaleCharsNotLeakedAfterClear() throws Exception {
189 final StrBuilder sb = new StrBuilder("secret_password_xyzzy_leak");
190
191 sb.clear();
192
193 sb.append("ok");
194
195
196 assertFalse(containsUtf16Be(SerializationUtils.serialize(sb), "xyzzy_leak"));
197 }
198
199 @Test
200 public void testStaleCharsNotLeakedAfterTruncate() throws Exception {
201 final StrBuilder sb = new StrBuilder("top_secret_key_material");
202
203 sb.delete(6, sb.length());
204
205 assertFalse(containsUtf16Be(SerializationUtils.serialize(sb), "secret_key_material"));
206 }
207 }