(SOLVED) Http woes on android

Hello hello,

I’m new-ish to zig, building a small android app. It’s mostly gone well! However, I’ve encountered a problem that has me out of my depth.

I’m targeting android 10, api level 29, arm64 processor. I am using zig 0.14.0. My code compiles fine, but trying to make an https request with std.http triggers a SIGSYS. Seccomp is disallowing syscall 291, which according to this arm64 syscall table is statx. Some googling indicates that android does indeed disallow statx (this is the whitelist). I’m completely out of ideas at this point, and starting to worry that the seccomp error message is a red herring triggered by zig trying and failing to unwind the stack trace? I don’t know, but I figured I’d stop going down rabbit holes and see if someone can help.

Here is the relevant crash log:

04-03 15:19:29.478 22764 28268 F libc    : Fatal signal 31 (SIGSYS), code 1 (SYS_SECCOMP) in tid 28268 (com.zig.minimal), pid 22764 (com.zig.minimal)
04-03 15:19:29.639 28273 28273 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
04-03 15:19:29.639 28273 28273 F DEBUG   : Revision: '14'
04-03 15:19:29.639 28273 28273 F DEBUG   : ABI: 'arm64'
04-03 15:19:29.640 28273 28273 F DEBUG   : Timestamp: 2025-04-03 15:19:29-0600
04-03 15:19:29.640 28273 28273 F DEBUG   : pid: 22764, tid: 28268, name: com.zig.minimal  >>> com.zig.minimal <<<
04-03 15:19:29.640 28273 28273 F DEBUG   : uid: 10696
04-03 15:19:29.640 28273 28273 F DEBUG   : signal 31 (SIGSYS), code 1 (SYS_SECCOMP), fault addr --------
04-03 15:19:29.640 28273 28273 F DEBUG   : Cause: seccomp prevented call to disallowed arm64 system call 291
04-03 15:19:29.640 28273 28273 F DEBUG   :     x0  000000000000003c  x1  0000007ee9d2993f  x2  0000000000001000  x3  00000000000000e3
04-03 15:19:29.640 28273 28273 F DEBUG   :     x4  0000007ef14792f0  x5  0000000000000000  x6  0000007ee9d224bd  x7  0000000000000001
04-03 15:19:29.640 28273 28273 F DEBUG   :     x8  0000000000000123  x9  0000007ef1479570  x10 0000007ef20a3020  x11 0000007ef14792f0
04-03 15:19:29.641 28273 28273 F DEBUG   :     x12 0000000000000030  x13 0000007ee9f5d930  x14 00000000ffffffff  x15 0000000000000073
04-03 15:19:29.641 28273 28273 F DEBUG   :     x16 0000007ee9f617d8  x17 0000007f81015848  x18 0000007e94be2000  x19 0000007ef187cf40
04-03 15:19:29.641 28273 28273 F DEBUG   :     x20 0000007ef20a3020  x21 0000007ef187cf30  x22 0000007ef147adc8  x23 000000000000003c
04-03 15:19:29.641 28273 28273 F DEBUG   :     x24 0000007ef187cf30  x25 000000000000003b  x26 0000007ef187cfc8  x27 0000007ee9e71cfc
04-03 15:19:29.641 28273 28273 F DEBUG   :     x28 0000007ef14799a8  x29 0000007ef14794c0
04-03 15:19:29.641 28273 28273 F DEBUG   :     sp  0000007ef14792e0  lr  0000007ee9e89e98  pc  0000007ee9e59818
04-03 15:19:29.641 28273 28273 F DEBUG   : 
04-03 15:19:29.641 28273 28273 F DEBUG   : backtrace:
04-03 15:19:29.641 28273 28273 F DEBUG   :       #00 pc 000000000014f818  /data/app/com.zig.minimal-qNIMES0OaxETnVriYNM_2A==/lib/arm64/libmain.so!libminimal.so (offset 0xd6000) (debug.Dwarf.ElfModule.load+8412)
04-03 15:19:29.641 28273 28273 F DEBUG   :       #01 pc 000000000017fe94  /data/app/com.zig.minimal-qNIMES0OaxETnVriYNM_2A==/lib/arm64/libmain.so!libminimal.so (offset 0xd6000) (http.Client.connectTcp+1340)
04-03 15:19:29.642 28273 28273 F DEBUG   :       #02 pc 0000000000167cf8  /data/app/com.zig.minimal-qNIMES0OaxETnVriYNM_2A==/lib/arm64/libmain.so!libminimal.so (offset 0xd6000) (posix.openatZ+544)

Here is the relevant code:

    const allocator = std.heap.c_allocator;
    var headerBuf: [4096]u8 = undefined;
    var responseBuffer: [4096 * 1024]u8 = undefined;

    var client = std.http.Client{ .allocator = allocator};
    defer client.deinit();

    const uri = std.Uri.parse("https://google.com") catch @panic("could not parse URI!");
    var request = client.open(.GET, uri, .{ .server_header_buffer = &headerBuf }) catch @panic("could not open URL");
    defer request.deinit();

    request.send() catch @panic("could not send HTTP request");
    request.finish() catch @panic("could not send HTTP request");
    request.wait() catch @panic("could not send HTTP request");

    _ = request.readAll(&responseBuffer) catch @panic("could not read server response!");
    std.debug.print("{s}\n", .{responseBuffer});

if I cut the std.debug.print line, I get a segfault instead, with an even shorter backtrace:

04-04 16:47:41.385   579  7193 F libc    : Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7ef1e00000 in tid 7193 (com.zig.minimal), pid 579 (com.zig.minimal)
04-04 16:47:41.451  1452  2028 D MdnieScenarioControlService:  packageName : com.zig.minimal    className : android.app.NativeActivity
04-04 16:47:41.554  7197  7197 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
04-04 16:47:41.555  7197  7197 F DEBUG   : Revision: '14'
04-04 16:47:41.555  7197  7197 F DEBUG   : ABI: 'arm64'
04-04 16:47:41.555  7197  7197 F DEBUG   : Timestamp: 2025-04-04 16:47:41-0600
04-04 16:47:41.555  7197  7197 F DEBUG   : pid: 579, tid: 7193, name: com.zig.minimal  >>> com.zig.minimal <<<
04-04 16:47:41.555  7197  7197 F DEBUG   : uid: 10698
04-04 16:47:41.555  7197  7197 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7ef1e00000
04-04 16:47:41.555  7197  7197 F DEBUG   :     x0  0000007ef1c95e60  x1  00000000000000aa  x2  0000000000400000  x3  0000007ee9bc8000
04-04 16:47:41.555  7197  7197 F DEBUG   :     x4  0000007efea033b8  x5  0000007efea2d540  x6  0000000000000000  x7  0000000000000001
04-04 16:47:41.555  7197  7197 F DEBUG   :     x8  0000007ef1e00010  x9  0000000000295e40  x10 0000000000400000  x11 0000000000000000
04-04 16:47:41.555  7197  7197 F DEBUG   :     x12 ffffffffffffffff  x13 0000000000000045  x14 0000000000000068  x15 0000000000000000
04-04 16:47:41.555  7197  7197 F DEBUG   :     x16 0000007ee9f2f030  x17 0000007ee9f17ddc  x18 0000007e94db0000  x19 0000007ef1c94e60
04-04 16:47:41.555  7197  7197 F DEBUG   :     x20 0000007ee9dbedac  x21 0000007ef2095e60  x22 0000007ef20bc020  x23 0000007ee95c8800
04-04 16:47:41.555  7197  7197 F DEBUG   :     x24 0000007ef20bb958  x25 0000000000000000  x26 00000000005ad5c0  x27 0000007ee95c8800
04-04 16:47:41.555  7197  7197 F DEBUG   :     x28 0000007ffcd6be30  x29 0000007ef1c94d70
04-04 16:47:41.555  7197  7197 F DEBUG   :     sp  0000007ef1c94d70  lr  0000007ee9e3d920  pc  0000007ee9f17e20
04-04 16:47:41.556  7197  7197 F DEBUG   :
04-04 16:47:41.556  7197  7197 F DEBUG   : backtrace:
04-04 16:47:41.556  7197  7197 F DEBUG   :       #00 pc 000000000023fe20  /data/app/com.zig.minimal--nAPJwe9720sa8I0bfmUfQ==/lib/arm64/libmain.so!libminimal.so (offset 0xd6000) (__mulxf3+452)
04-04 16:47:41.556  7197  7197 F DEBUG   :       #01 pc 000000000016591c  /data/app/com.zig.minimal--nAPJwe9720sa8I0bfmUfQ==/lib/arm64/libmain.so!libminimal.so (offset 0xd6000) (array_hash_map.ArrayHashMapUnmanaged(u64,debug.Dwarf.CompileUnit.SrcLocCache.LineEntry,array_hash_map.AutoContext(u64),false).sortContextInternal__anon_10689+6900)
04-04 16:47:41.556  7197  7197 F DEBUG   :       #02 pc 00000000000e6da8  /data/app/com.zig.minimal--nAPJwe9720sa8I0bfmUfQ==/lib/arm64/libmain.so!libminimal.so (offset 0xd6000)
04-04 16:47:41.650   908   908 I SurfaceFlinger:      DEVICE | 0x78543b70b0 | 0002 | RGBA_8888 |   0.0    0.0 1080.0 2220.0 |    0    0 1080 2220 | Splash Screen com.zig.minimal#0

I have also tried compiling libcurl for android and using some zig bindings for that instead:

    var allocBuffer: [1024 * 100]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&allocBuffer);
    const fbaAlloc = fba.allocator();

    const ca_bundle = curl.allocCABundle(fbaAlloc) catch @panic("OOM");
    defer ca_bundle.deinit();
    const easy = curl.Easy.init(fbaAlloc, .{
        .ca_bundle = ca_bundle
    }) catch @panic("OOM");
    defer easy.deinit();
    const response = easy.get("https://google.com") catch @panic("OOM");
    defer response.deinit();

this fails with an extremely familiar crash log…

04-06 18:03:41.690 11093 15819 F libc    : Fatal signal 31 (SIGSYS), code 1 (SYS_SECCOMP) in tid 15819 (com.zig.minimal), pid 11093 (com.zig.minimal)
04-06 18:03:41.779  1452  2028 D MdnieScenarioControlService:  packageName : com.zig.minimal    className : android.app.NativeActivity
04-06 18:03:41.842 15824 15824 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
04-06 18:03:41.842 15824 15824 F DEBUG   : Revision: '14'
04-06 18:03:41.842 15824 15824 F DEBUG   : ABI: 'arm64'
04-06 18:03:41.843 15824 15824 F DEBUG   : Timestamp: 2025-04-06 18:03:41-0600
04-06 18:03:41.843 15824 15824 F DEBUG   : pid: 11093, tid: 15819, name: com.zig.minimal  >>> com.zig.minimal <<<
04-06 18:03:41.843 15824 15824 F DEBUG   : uid: 10730
04-06 18:03:41.843 15824 15824 F DEBUG   : signal 31 (SIGSYS), code 1 (SYS_SECCOMP), fault addr --------
04-06 18:03:41.843 15824 15824 F DEBUG   : Cause: seccomp prevented call to disallowed arm64 system call 291
04-06 18:03:41.843 15824 15824 F DEBUG   :     x0  000000000000003c  x1  0000007ee9949ac2  x2  0000000000001000  x3  00000000000000e3
04-06 18:03:41.843 15824 15824 F DEBUG   :     x4  0000007ef2068f60  x5  0000000000000000  x6  0000000000000000  x7  000000008019424c
04-06 18:03:41.843 15824 15824 F DEBUG   :     x8  0000000000000123  x9  0000007ef2069270  x10 0000007ef2085020  x11 0000007ef2068f60
04-06 18:03:41.843 15824 15824 F DEBUG   :     x12 0000000000000030  x13 0000007ef2083b90  x14 0000007ef2083b90  x15 0000400000000000
04-06 18:03:41.843 15824 15824 F DEBUG   :     x16 0000007ee9f5bb88  x17 0000007f81015848  x18 0000007e949ae000  x19 000000000000003c
04-06 18:03:41.843 15824 15824 F DEBUG   :     x20 000000000000003c  x21 0000007ef20826b8  x22 000000000000003b  x23 0000007ef2082773
04-06 18:03:41.843 15824 15824 F DEBUG   :     x24 000000000000000a  x25 0000007ee9b9f428  x26 0000007ee99439fe  x27 0000007ef2085020
04-06 18:03:41.843 15824 15824 F DEBUG   :     x28 0000007ef20826f0  x29 0000007ef2069130
04-06 18:03:41.843 15824 15824 F DEBUG   :     sp  0000007ef2068f50  lr  0000007ee9ba220c  pc  0000007ee9b8c1e0
04-06 18:03:41.846 15824 15824 F DEBUG   :
04-06 18:03:41.846 15824 15824 F DEBUG   : backtrace:
04-06 18:03:41.846 15824 15824 F DEBUG   :       #00 pc 00000000003ba1e0  /data/app/com.zig.minimal-E2zM0KT9OvAFUNxasQMgxw==/lib/arm64/libmain.so!libminimal.so (offset 0x342000) (debug.Dwarf.ElfModule.load+6728)
04-06 18:03:41.846 15824 15824 F DEBUG   :       #01 pc 00000000003d0208  /data/app/com.zig.minimal-E2zM0KT9OvAFUNxasQMgxw==/lib/arm64/libmain.so!libminimal.so (offset 0x342000) (heap.arena_allocator.ArenaAllocator.alloc+672)
04-06 18:03:41.846 15824 15824 F DEBUG   :       #02 pc 00000000003cd424  /data/app/com.zig.minimal-E2zM0KT9OvAFUNxasQMgxw==/lib/arm64/libmain.so!libminimal.so (offset 0x342000) (debug.Dwarf.parseFormValue+2768)
04-06 18:03:41.846 15824 15824 F DEBUG   :       #03 pc 0000000000351e48  /data/app/com.zig.minimal-E2zM0KT9OvAFUNxasQMgxw==/lib/arm64/libmain.so!libminimal.so (offset 0x342000)

Solved by using libcurl and supplying the certificate directly rather than scanning for it.

    //ca cert acquired from https://curl.se/docs/caextract.html
    const ca_file = @embedFile("cacert.pem");
    var ca_blob = curl.Buffer.init(allocator);
    defer ca_blob.deinit();
    ca_blob.appendSlice(ca_file) catch panic("could not load CA cert!", .{});
    const easy = curl.Easy.init(allocator, .{
        .ca_bundle = ca_blob
    }) catch panic("OOM", .{});
    defer easy.deinit();
    const response = easy.get("https://google.com") catch panic("OOM", .{});
    defer response.deinit();
    var maybeResponse = response.body orelse std.ArrayList(u8).init(allocator);
    const responseBody = maybeResponse.toOwnedSliceSentinel(0) catch panic("OOM", .{});
2 Likes