Skip to content

Commit 2e3287d

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 01ca1f5 + 2454642 commit 2e3287d

1,372 files changed

Lines changed: 1185 additions & 5 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/VOUCHED.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ adrum
2626
aindriu80
2727
alanmoyano
2828
alexfeijoo44
29+
alexjuca
2930
amadeus
3031
andrejdaskalov
3132
atomk
@@ -38,6 +39,7 @@ bitigchi
3839
bkircher
3940
bo2themax
4041
brentschroeter
42+
cespare
4143
charliie-dev
4244
chernetskyi
4345
craziestowl
@@ -46,9 +48,11 @@ d-dudas
4648
daiimus
4749
damyanbogoev
4850
danulqua
51+
dervedro
4952
diaaeddin
5053
doprz
5154
douglance
55+
drepper
5256
elias8
5357
ephemera
5458
eriksremess
@@ -94,6 +98,7 @@ miguelelgallo
9498
mihi314
9599
mikailmm
96100
misairuzame
101+
mischief
97102
mitchellh
98103
miupa
99104
mrmage

.github/workflows/test.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ jobs:
102102
- test-gtk
103103
- test-sentry-linux
104104
- test-i18n
105+
- test-fuzz-libghostty
105106
- test-macos
106107
- pinact
107108
- prettier
@@ -1010,6 +1011,51 @@ jobs:
10101011
run: |
10111012
nix develop -c zig build -Di18n=${{ matrix.i18n }}
10121013
1014+
test-fuzz-libghostty:
1015+
name: Build test/fuzz-libghostty
1016+
runs-on: namespace-profile-ghostty-sm
1017+
needs: test
1018+
env:
1019+
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
1020+
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
1021+
steps:
1022+
- name: Checkout code
1023+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
1024+
1025+
- name: Setup Cache
1026+
uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9 # v1.4.2
1027+
with:
1028+
path: |
1029+
/nix
1030+
/zig
1031+
1032+
# Install Nix and use that to run our tests so our environment matches exactly.
1033+
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
1034+
with:
1035+
nix_path: nixpkgs=channel:nixos-unstable
1036+
- uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16
1037+
with:
1038+
name: ghostty
1039+
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
1040+
1041+
- name: Install AFL++ and LLVM
1042+
run: |
1043+
sudo apt-get update
1044+
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y afl++ llvm
1045+
1046+
- name: Verify AFL++ and LLVM are available
1047+
run: |
1048+
afl-cc --version
1049+
if command -v llvm-config >/dev/null 2>&1; then
1050+
llvm-config --version
1051+
else
1052+
llvm-config-18 --version
1053+
fi
1054+
1055+
- name: Build fuzzer harness
1056+
run: |
1057+
nix develop -c sh -c 'cd test/fuzz-libghostty && zig build'
1058+
10131059
zig-fmt:
10141060
if: github.repository == 'ghostty-org/ghostty' && needs.skip.outputs.skip != 'true' && needs.skip.outputs.zig == 'true'
10151061
needs: skip

.prettierignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ website/.next
1919

2020
# shaders
2121
*.frag
22+
23+
# fuzz corpus files
24+
test/fuzz-libghostty/corpus/
25+
test/fuzz-libghostty/afl-out/

CONTRIBUTING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ on a system of trust, and AI has unfortunately made it so we can no
4747
longer trust-by-default because it makes it too trivial to generate
4848
plausible-looking but actually low-quality contributions.
4949

50+
## Contributors Prior to the Vouch System
51+
52+
If you contributed to Ghostty prior to the introduction
53+
of the vouch system and wish to continue contributing, you were not
54+
automatically added to the [list of vouched users](.github/VOUCHED.td). You will need to follow the same
55+
process as a first-time contributor to be vouched.
56+
5057
## Denouncement System
5158

5259
If you repeatedly break the rules of this document or repeatedly

pkg/afl++/LICENSE

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Based on zig-afl-kit: https://github.com/kristoff-it/zig-afl-kit
2+
3+
MIT License
4+
5+
Copyright (c) 2024 Loris Cro
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.

pkg/afl++/afl.c

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#include <limits.h>
2+
#include <signal.h>
3+
#include <stdint.h>
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
#include <string.h>
7+
#include <unistd.h>
8+
9+
// AFL++ fuzzer harness for Zig fuzz targets.
10+
//
11+
// This file is the C "glue" that connects AFL++'s runtime to Zig-defined
12+
// fuzz test functions. We can't use AFL++'s compiler wrappers (afl-clang,
13+
// afl-gcc) because the code under test is compiled with Zig, so we manually
14+
// expand the AFL macros (__AFL_INIT, __AFL_LOOP, __AFL_FUZZ_INIT, etc.) and
15+
// wire up the sanitizer coverage symbols ourselves.
16+
17+
// To ensure checks are not optimized out it is recommended to disable
18+
// code optimization for the fuzzer harness main()
19+
#pragma clang optimize off
20+
#pragma GCC optimize("O0")
21+
22+
// Zig-exported entry points. zig_fuzz_init() performs one-time setup and
23+
// zig_fuzz_test() runs one fuzz iteration on the given input buffer.
24+
// The Zig object should export these.
25+
void zig_fuzz_init();
26+
void zig_fuzz_test(unsigned char*, size_t);
27+
28+
// Linker-provided symbols marking the boundaries of the __sancov_guards
29+
// section. These must be declared extern so the linker provides the actual
30+
// section boundaries from the instrumented code, rather than creating new
31+
// variables that shadow them. On macOS (Mach-O), the linker uses a different
32+
// naming convention for section boundaries than Linux (ELF), so we use asm
33+
// labels to reference them.
34+
#ifdef __APPLE__
35+
extern uint32_t __start___sancov_guards __asm(
36+
"section$start$__DATA$__sancov_guards");
37+
extern uint32_t __stop___sancov_guards __asm(
38+
"section$end$__DATA$__sancov_guards");
39+
#else
40+
extern uint32_t __start___sancov_guards;
41+
extern uint32_t __stop___sancov_guards;
42+
#endif
43+
44+
// Provided by afl-compiler-rt; initializes the guard array used by
45+
// SanitizerCoverage's trace-pc-guard instrumentation mode.
46+
void __sanitizer_cov_trace_pc_guard_init(uint32_t*, uint32_t*);
47+
48+
// Stubs for sanitizer coverage callbacks that the Zig-compiled code references
49+
// but AFL's runtime (afl-compiler-rt) does not provide. Without these, linking
50+
// would fail with undefined symbol errors.
51+
__attribute__((visibility("default"))) __attribute__((
52+
tls_model("initial-exec"))) _Thread_local uintptr_t __sancov_lowest_stack;
53+
void __sanitizer_cov_trace_pc_indir() {}
54+
void __sanitizer_cov_8bit_counters_init() {}
55+
void __sanitizer_cov_pcs_init() {}
56+
57+
// Manual expansion of __AFL_FUZZ_INIT().
58+
//
59+
// Enables shared-memory fuzzing: AFL++ writes test cases directly into
60+
// shared memory (__afl_fuzz_ptr) instead of passing them via stdin, which
61+
// is much faster. When not running under AFL++ (e.g. standalone execution),
62+
// __afl_fuzz_ptr will be NULL and we fall back to reading from stdin into
63+
// __afl_fuzz_alt (a 1 MB static buffer).
64+
int __afl_sharedmem_fuzzing = 1;
65+
extern __attribute__((visibility("default"))) unsigned int* __afl_fuzz_len;
66+
extern __attribute__((visibility("default"))) unsigned char* __afl_fuzz_ptr;
67+
unsigned char __afl_fuzz_alt[1048576];
68+
unsigned char* __afl_fuzz_alt_ptr = __afl_fuzz_alt;
69+
70+
int main(int argc, char** argv) {
71+
// Tell AFL's coverage runtime about our guard section so it can track
72+
// which edges in the instrumented Zig code have been hit.
73+
__sanitizer_cov_trace_pc_guard_init(&__start___sancov_guards,
74+
&__stop___sancov_guards);
75+
76+
// Manual expansion of __AFL_INIT() — deferred fork server mode.
77+
//
78+
// The magic string "##SIG_AFL_DEFER_FORKSRV##" is embedded in the binary
79+
// so AFL++'s tooling can detect that this harness uses deferred fork
80+
// server initialization. The `volatile` + `used` attributes prevent the
81+
// compiler/linker from stripping it. We then call __afl_manual_init() to
82+
// start the fork server at this point (after our setup) rather than at
83+
// the very beginning of main().
84+
static volatile const char* _A __attribute__((used, unused));
85+
_A = (const char*)"##SIG_AFL_DEFER_FORKSRV##";
86+
#ifdef __APPLE__
87+
__attribute__((visibility("default"))) void _I(void) __asm__(
88+
"___afl_manual_init");
89+
#else
90+
__attribute__((visibility("default"))) void _I(void) __asm__(
91+
"__afl_manual_init");
92+
#endif
93+
_I();
94+
95+
zig_fuzz_init();
96+
97+
// Manual expansion of __AFL_FUZZ_TESTCASE_BUF.
98+
// Use shared memory buffer if available, otherwise fall back to the
99+
// static buffer (for standalone/non-AFL execution).
100+
unsigned char* buf = __afl_fuzz_ptr ? __afl_fuzz_ptr : __afl_fuzz_alt_ptr;
101+
102+
// Manual expansion of __AFL_LOOP(UINT_MAX) — persistent mode loop.
103+
//
104+
// Persistent mode keeps the process alive across many test cases instead
105+
// of fork()'ing for each one, dramatically improving throughput. The magic
106+
// string "##SIG_AFL_PERSISTENT##" signals to AFL++ that this binary
107+
// supports persistent mode. __afl_persistent_loop() returns non-zero
108+
// while there are more inputs to process.
109+
//
110+
// When connected to AFL++, we loop UINT_MAX times (essentially forever,
111+
// AFL will restart us periodically). When running standalone, we loop
112+
// once so the harness can be used for manual testing/reproduction.
113+
while (({
114+
static volatile const char* _B __attribute__((used, unused));
115+
_B = (const char*)"##SIG_AFL_PERSISTENT##";
116+
extern __attribute__((visibility("default"))) int __afl_connected;
117+
#ifdef __APPLE__
118+
__attribute__((visibility("default"))) int _L(unsigned int) __asm__(
119+
"___afl_persistent_loop");
120+
#else
121+
__attribute__((visibility("default"))) int _L(unsigned int) __asm__(
122+
"__afl_persistent_loop");
123+
#endif
124+
_L(__afl_connected ? UINT_MAX : 1);
125+
})) {
126+
// Manual expansion of __AFL_FUZZ_TESTCASE_LEN.
127+
// In shared-memory mode, the length is provided directly by AFL++.
128+
// In standalone mode, we read from stdin into the fallback buffer.
129+
int len =
130+
__afl_fuzz_ptr ? *__afl_fuzz_len
131+
: (*__afl_fuzz_len = read(0, __afl_fuzz_alt_ptr, 1048576)) == 0xffffffff
132+
? 0
133+
: *__afl_fuzz_len;
134+
135+
if (len >= 0) {
136+
zig_fuzz_test(buf, len);
137+
}
138+
}
139+
140+
return 0;
141+
}

pkg/afl++/build.zig

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const std = @import("std");
2+
3+
/// Creates a build step that produces an AFL++-instrumented fuzzing
4+
/// executable.
5+
///
6+
/// Returns a `LazyPath` to the resulting fuzzing executable.
7+
pub fn addInstrumentedExe(
8+
b: *std.Build,
9+
obj: *std.Build.Step.Compile,
10+
) std.Build.LazyPath {
11+
// Force the build system to produce the binary artifact even though we
12+
// only consume the LLVM bitcode below. Without this, the dependency
13+
// tracking doesn't wire up correctly.
14+
_ = obj.getEmittedBin();
15+
16+
const pkg = b.dependencyFromBuildZig(
17+
@This(),
18+
.{},
19+
);
20+
21+
const afl_cc = b.addSystemCommand(&.{
22+
b.findProgram(&.{"afl-cc"}, &.{}) catch
23+
@panic("Could not find 'afl-cc', which is required to build"),
24+
"-O3",
25+
});
26+
afl_cc.addArg("-o");
27+
const fuzz_exe = afl_cc.addOutputFileArg(obj.name);
28+
afl_cc.addFileArg(pkg.path("afl.c"));
29+
afl_cc.addFileArg(obj.getEmittedLlvmBc());
30+
return fuzz_exe;
31+
}
32+
33+
/// Creates a run step that invokes `afl-fuzz` with the given instrumented
34+
/// executable, input corpus directory, and output directory.
35+
///
36+
/// Returns the `Run` step so callers can wire it into a build step.
37+
pub fn addFuzzerRun(
38+
b: *std.Build,
39+
exe: std.Build.LazyPath,
40+
corpus_dir: std.Build.LazyPath,
41+
output_dir: std.Build.LazyPath,
42+
) *std.Build.Step.Run {
43+
const run = b.addSystemCommand(&.{
44+
b.findProgram(&.{"afl-fuzz"}, &.{}) catch
45+
@panic("Could not find 'afl-fuzz', which is required to run"),
46+
"-i",
47+
});
48+
run.addDirectoryArg(corpus_dir);
49+
run.addArgs(&.{"-o"});
50+
run.addDirectoryArg(output_dir);
51+
run.addArgs(&.{"--"});
52+
run.addFileArg(exe);
53+
run.addArgs(&.{"@@"});
54+
return run;
55+
}
56+
57+
// Required so `zig build` works although it does nothing.
58+
pub fn build(b: *std.Build) !void {
59+
_ = b;
60+
}

pkg/afl++/build.zig.zon

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.{
2+
.name = .afl_plus_plus,
3+
.fingerprint = 0x465bc4bebb188f16,
4+
.version = "0.1.0",
5+
.dependencies = .{},
6+
.paths = .{
7+
"build.zig",
8+
"build.zig.zon",
9+
"afl.c",
10+
},
11+
}

src/Surface.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2973,6 +2973,9 @@ fn maybeHandleBinding(
29732973
// If our action was "ignore" then we return the special input
29742974
// effect of "ignored".
29752975
for (actions) |action| if (action == .ignore) {
2976+
// If we're in a sequence, clear it.
2977+
self.endKeySequence(.drop, .retain);
2978+
29762979
return .ignored;
29772980
};
29782981
}

src/build/GitVersion.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub fn detect(b: *std.Build) !Version {
3939

4040
const short_hash = short_hash: {
4141
const output = b.runAllowFail(
42-
&[_][]const u8{ "git", "-C", b.build_root.path orelse ".", "log", "--pretty=format:%h", "-n", "1" },
42+
&[_][]const u8{ "git", "-C", b.build_root.path orelse ".", "-c", "log.showSignature=false", "log", "--pretty=format:%h", "-n", "1" },
4343
&code,
4444
.Ignore,
4545
) catch |err| switch (err) {

0 commit comments

Comments
 (0)