Skip to content

Non interactive input (pipes) makes binaries crash #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
nollium opened this issue Dec 26, 2024 · 5 comments
Open

Non interactive input (pipes) makes binaries crash #52

nollium opened this issue Dec 26, 2024 · 5 comments

Comments

@nollium
Copy link

nollium commented Dec 26, 2024

I'm using dockerc for a simple program that takes input via STDIN.
It works well when passing data interactively (i.e: the user entering it directly in their terminal), but when using any non-interactive inputs, the dockerc generated binaries crash instantly.

POC

main.c:

#include <stdio.h>

int main() {
    printf("Hello, world!\n");
    return 0;
}

Dockerfile:

# Build stage
FROM alpine:latest AS builder
WORKDIR /build
RUN apk add --no-cache gcc musl-dev
COPY main.c .
RUN gcc -O0 -o c-program main.c -no-pie -fno-stack-protector

# Final stage
FROM alpine:latest

WORKDIR /challenge
COPY --from=builder /build/c-program .
ENTRYPOINT ["./c-program"]

build.sh

#!/bin/bash

docker build -t test-image .
dockerc_x86-64  --image "docker-daemon:test-image:latest" --output "dockerc-program"

Output

Launching normally (works):

./dockerc-program 
newuidmap failed, falling back to single user mapping
newgidmap failed, falling back to single group mapping
unknown argument ignored: lazytime
Hello, world!

Launching with non-interactive STDIN:

cat | ./dockerc-program 
newuidmap failed, falling back to single user mapping
newgidmap failed, falling back to single group mapping
unknown argument ignored: lazytime
thread 1032321 panic: failed to run container (status/errno: 25) (-1): tcgetattr

Unwind error at address `exe:0x1083476` (error.InvalidDebugInfo), trace may be incomplete

thread 1032307 panic: container didn't exist normally : 134

Unwind error at address `exe:0x1083476` (error.InvalidDebugInfo), trace may be incomplete


Aborted (core dumped)

For the record, the normal behaviour is well implemented by docker run:

cat | sudo docker run -i test-image
Hello, world!

I havent included actual user input handling in this example to pinpoint the fact that dockerc binaries crash in all cases, but docker run -i handles it correctly too.

This crash prevents using dockerc binaries as regular "UNIX-y" command-line utilities.

@NilsIrl
Copy link
Owner

NilsIrl commented Dec 26, 2024

I would debug this by running the crun binary under the same conditions and see if it fails in the same way. If it doesn't fail in the same way then I would try to investigate why it doesn't fail and replicate this behavior in dockerc.

Looking at the source code for crun maybe setting console_socket could fix this but I am not sure this is the correct fix.

@NilsIrl
Copy link
Owner

NilsIrl commented Dec 26, 2024

Otherwise maybe this could work: https://manpages.ubuntu.com/manpages/trusty/man8/open_init_pty.8.html

Or some sort of program that fakes a terminal, I am unfortunately not too familiar with the terminal API.

@nollium
Copy link
Author

nollium commented Dec 26, 2024

Thanks for the quick answer!

open_init_pty does work, but using such a program would be kind of a hacky solution for my use-case

I'm not familiar with low-level container components, I tried running runc from podman and did not encounter the issue:

cat | podman run --runtime /usr/bin/crun -i test 
Hello, world!

I then tried running runc directly using the same image:

Setting up the rootfs and spec

mkdir -p bundle/rootfs
cd bundle
crun spec
podman build -t dockerc-program ..
podman export $(podman create dockerc-program) | tar -C rootfs -xf -
crun run dockerc-container

Running crun

crun run dockerc-container <<< id

2024-12-26T21:36:17.195490Z: tcgetattr: Inappropriate ioctl for device

The same error seems to happen, however, I noticed something in the spec generated by crun:
config.json:

{
	"ociVersion": "1.0.0",
	"process": {
		"terminal": true,
		"user": {
			"uid": 0,
			"gid": 0
		},
		"args": [
			"sh"
		],
...

terminal is set to true by default, by switching this value to false, the expected behavior is met:

crun run dockerc-container <<< id

uid=0(root) gid=0(root)

In this example, the /bin/sh binary is ran for testing purposes.

Could it be that dockerc tries to run the binary in "terminal" mode by default ?
Maybe there could be a way for the generated binaries to check if they are ran in an interactive tty before choosing the terminal value crun is using ?

I think the "detection" part can be achieved by using isatty() on fd 0

@nollium
Copy link
Author

nollium commented Dec 27, 2024

After some troubleshooting, I managed to set the terminal flag to false in dockerc, here's the patch:

diff --git a/src/main.zig b/src/main.zig
index da5beca..0a87ef8 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -179,6 +179,8 @@ fn getContainerFromArgs(file: std.fs.File, rootfs_absolute_path: []const u8, par
             const processVal = object.getPtr("process") orelse @panic("no process key");
             switch (processVal.*) {
                 .object => |*process| {
+                    try process.put("terminal", std.json.Value{ .bool = false });
+
                     const argsVal = process.getPtr("args") orelse @panic("no args key");
                     switch (argsVal.*) {
                         .array => |*argsArr| {

I don't know if I will be able to present you with a full, clean PR for this, as I likely lack the time and technical knowledge, it is also the first time i've written any line of zig ever

@NilsIrl
Copy link
Owner

NilsIrl commented Dec 27, 2024

Great work, thanks for debugging this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants