Skip to content

Commit cd9ecab

Browse files
committed
feat: add hooks for temporal parse and string compare in equals methods
1 parent 50a0e8f commit cd9ecab

File tree

3 files changed

+238
-0
lines changed

3 files changed

+238
-0
lines changed

src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@
1919
import com.code_intelligence.jazzer.api.HookType;
2020
import com.code_intelligence.jazzer.api.MethodHook;
2121
import java.lang.invoke.MethodHandle;
22+
import java.time.Duration;
23+
import java.time.Instant;
24+
import java.time.LocalDate;
25+
import java.time.LocalDateTime;
26+
import java.time.LocalTime;
27+
import java.time.MonthDay;
28+
import java.time.OffsetDateTime;
29+
import java.time.OffsetTime;
30+
import java.time.Period;
31+
import java.time.Year;
32+
import java.time.YearMonth;
33+
import java.time.ZoneId;
34+
import java.time.ZoneOffset;
35+
import java.time.ZonedDateTime;
2236
import java.util.*;
2337

2438
@SuppressWarnings("unused")
@@ -234,6 +248,176 @@ public static void equals(
234248
}
235249
}
236250

251+
private static final LocalDate FALLBACK_LOCAL_DATE = LocalDate.of(2000, 1, 1);
252+
private static final ClassValue<Optional<String>> TEMPORAL_PARSE_FALLBACKS =
253+
new ClassValue<Optional<String>>() {
254+
@Override
255+
protected Optional<String> computeValue(Class<?> type) {
256+
if (type == Duration.class) return Optional.of(Duration.ZERO.toString());
257+
if (type == Instant.class) return Optional.of(Instant.EPOCH.toString());
258+
if (type == LocalDate.class) return Optional.of(FALLBACK_LOCAL_DATE.toString());
259+
if (type == LocalDateTime.class)
260+
return Optional.of(FALLBACK_LOCAL_DATE.atStartOfDay().toString());
261+
if (type == LocalTime.class) return Optional.of(LocalTime.MIDNIGHT.toString());
262+
if (type == OffsetDateTime.class) {
263+
return Optional.of(
264+
OffsetDateTime.of(FALLBACK_LOCAL_DATE, LocalTime.MIDNIGHT, ZoneOffset.UTC)
265+
.toString());
266+
}
267+
if (type == OffsetTime.class) {
268+
return Optional.of(OffsetTime.of(LocalTime.MIDNIGHT, ZoneOffset.UTC).toString());
269+
}
270+
if (type == Period.class) return Optional.of(Period.ZERO.toString());
271+
if (type == Year.class)
272+
return Optional.of(Year.of(FALLBACK_LOCAL_DATE.getYear()).toString());
273+
if (type == YearMonth.class) {
274+
return Optional.of(
275+
YearMonth.of(FALLBACK_LOCAL_DATE.getYear(), FALLBACK_LOCAL_DATE.getMonth())
276+
.toString());
277+
}
278+
if (type == MonthDay.class)
279+
return Optional.of(MonthDay.from(FALLBACK_LOCAL_DATE).toString());
280+
if (type == ZonedDateTime.class) {
281+
return Optional.of(
282+
ZonedDateTime.of(FALLBACK_LOCAL_DATE, LocalTime.MIDNIGHT, ZoneId.of("Europe/Paris"))
283+
.toString());
284+
}
285+
return Optional.empty();
286+
}
287+
};
288+
289+
@MethodHook(
290+
type = HookType.AFTER,
291+
targetClassName = "java.time.Duration",
292+
targetMethod = "equals",
293+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
294+
@MethodHook(
295+
type = HookType.AFTER,
296+
targetClassName = "java.time.Instant",
297+
targetMethod = "equals",
298+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
299+
@MethodHook(
300+
type = HookType.AFTER,
301+
targetClassName = "java.time.LocalDate",
302+
targetMethod = "equals",
303+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
304+
@MethodHook(
305+
type = HookType.AFTER,
306+
targetClassName = "java.time.LocalDateTime",
307+
targetMethod = "equals",
308+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
309+
@MethodHook(
310+
type = HookType.AFTER,
311+
targetClassName = "java.time.LocalTime",
312+
targetMethod = "equals",
313+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
314+
@MethodHook(
315+
type = HookType.AFTER,
316+
targetClassName = "java.time.MonthDay",
317+
targetMethod = "equals",
318+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
319+
@MethodHook(
320+
type = HookType.AFTER,
321+
targetClassName = "java.time.OffsetDateTime",
322+
targetMethod = "equals",
323+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
324+
@MethodHook(
325+
type = HookType.AFTER,
326+
targetClassName = "java.time.OffsetTime",
327+
targetMethod = "equals",
328+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
329+
@MethodHook(
330+
type = HookType.AFTER,
331+
targetClassName = "java.time.Period",
332+
targetMethod = "equals",
333+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
334+
@MethodHook(
335+
type = HookType.AFTER,
336+
targetClassName = "java.time.Year",
337+
targetMethod = "equals",
338+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
339+
@MethodHook(
340+
type = HookType.AFTER,
341+
targetClassName = "java.time.YearMonth",
342+
targetMethod = "equals",
343+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
344+
@MethodHook(
345+
type = HookType.AFTER,
346+
targetClassName = "java.time.ZonedDateTime",
347+
targetMethod = "equals",
348+
targetMethodDescriptor = "(Ljava/lang/Object;)Z")
349+
public static void temporalEquals(
350+
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean areEqual) {
351+
if (!areEqual
352+
&& arguments.length == 1
353+
&& arguments[0] != null
354+
&& thisObject.getClass() == arguments[0].getClass()) {
355+
TraceDataFlowNativeCallbacks.traceStrcmp(
356+
thisObject.toString(), arguments[0].toString(), 1, hookId);
357+
}
358+
}
359+
360+
@MethodHook(
361+
type = HookType.REPLACE,
362+
targetClassName = "java.time.Instant",
363+
targetMethod = "parse")
364+
@MethodHook(
365+
type = HookType.REPLACE,
366+
targetClassName = "java.time.LocalDate",
367+
targetMethod = "parse")
368+
@MethodHook(
369+
type = HookType.REPLACE,
370+
targetClassName = "java.time.LocalDateTime",
371+
targetMethod = "parse")
372+
@MethodHook(
373+
type = HookType.REPLACE,
374+
targetClassName = "java.time.LocalTime",
375+
targetMethod = "parse")
376+
@MethodHook(
377+
type = HookType.REPLACE,
378+
targetClassName = "java.time.OffsetDateTime",
379+
targetMethod = "parse")
380+
@MethodHook(
381+
type = HookType.REPLACE,
382+
targetClassName = "java.time.OffsetTime",
383+
targetMethod = "parse")
384+
@MethodHook(
385+
type = HookType.REPLACE,
386+
targetClassName = "java.time.ZonedDateTime",
387+
targetMethod = "parse")
388+
@MethodHook(type = HookType.REPLACE, targetClassName = "java.time.Year", targetMethod = "parse")
389+
@MethodHook(
390+
type = HookType.REPLACE,
391+
targetClassName = "java.time.YearMonth",
392+
targetMethod = "parse")
393+
@MethodHook(
394+
type = HookType.REPLACE,
395+
targetClassName = "java.time.MonthDay",
396+
targetMethod = "parse")
397+
@MethodHook(
398+
type = HookType.REPLACE,
399+
targetClassName = "java.time.Duration",
400+
targetMethod = "parse")
401+
@MethodHook(type = HookType.REPLACE, targetClassName = "java.time.Period", targetMethod = "parse")
402+
public static Object temporalParse(
403+
MethodHandle method, Object alwaysNull, Object[] arguments, int hookId) throws Throwable {
404+
try {
405+
return method.invokeWithArguments(arguments);
406+
} catch (Throwable throwable) {
407+
if (throwable instanceof Error) {
408+
throw throwable;
409+
}
410+
411+
if (arguments[0] != null) {
412+
String fallback = TEMPORAL_PARSE_FALLBACKS.get(method.type().returnType()).orElse(null);
413+
if (fallback != null) {
414+
TraceDataFlowNativeCallbacks.traceStrcmp(arguments[0].toString(), fallback, 1, hookId);
415+
}
416+
}
417+
throw throwable;
418+
}
419+
}
420+
237421
@MethodHook(type = HookType.AFTER, targetClassName = "java.util.Objects", targetMethod = "equals")
238422
public static void genericObjectsEquals(
239423
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean areEqual) {

tests/BUILD.bazel

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,20 @@ java_fuzz_target_test(
774774
],
775775
)
776776

777+
java_fuzz_target_test(
778+
name = "TimeParseFuzzer",
779+
srcs = ["src/test/java/com/example/TimeParseFuzzer.java"],
780+
allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium"],
781+
fuzzer_args = [
782+
"-runs=1000000",
783+
],
784+
target_class = "com.example.TimeParseFuzzer",
785+
verify_crash_reproducer = False,
786+
deps = [
787+
"//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
788+
],
789+
)
790+
777791
sh_test(
778792
name = "jazzer_from_path_test",
779793
srcs = ["src/test/shell/jazzer_from_path_test.sh"],
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2024 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium;
20+
import com.code_intelligence.jazzer.mutation.annotation.Ascii;
21+
import com.code_intelligence.jazzer.mutation.annotation.NotNull;
22+
import java.time.OffsetDateTime;
23+
import java.time.format.DateTimeParseException;
24+
25+
public final class TimeParseFuzzer {
26+
private static final OffsetDateTime TARGET_DATE_TIME =
27+
OffsetDateTime.parse("2001-12-04T00:00:00-05:00");
28+
29+
private TimeParseFuzzer() {}
30+
31+
public static void fuzzerTestOneInput(@NotNull @Ascii String offsetDateInput) {
32+
try {
33+
OffsetDateTime offsetDateTime = OffsetDateTime.parse(offsetDateInput);
34+
if (TARGET_DATE_TIME.equals(offsetDateTime)) {
35+
throw new FuzzerSecurityIssueMedium();
36+
}
37+
} catch (DateTimeParseException ignored) {
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)