Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: ci

on:
push:
branches: [main]
pull_request:

jobs:
build:
# Ninguno de los dos puede probar la plataforma del otro en local:
# esta matriz es lo único que avisa cuando se rompe la otra.
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: make build
- run: make test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ CMakeFiles/
CMakeCache.txt
cmake_install.cmake
Makefile
!/Makefile
install_manifest.txt
compile_commands.json

Expand Down
59 changes: 59 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.20)
project(omicron LANGUAGES C)

# Este archivo lista TODOS los archivos del proyecto, incluidos los vacíos.
# La idea es no volver a tocarlo: es el que más conflictos de git genera.

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS ON) # gnu11: hace falta para POSIX/BSD (termios, ioctl)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
endif()

option(OMICRON_WERROR "Tratar warnings como errores" ON)
add_compile_options(-Wall -Wextra -Wshadow -Wvla)
if(OMICRON_WERROR)
add_compile_options(-Werror)
endif()

include_directories(include)

# --- Selección de plataforma -------------------------------------------------
# Nunca un sandbox_generic.c que no haga nada: un binario que dice que aísla
# y no aísla es peor que no tener binario. Si no hay implementación, se aborta.
if(APPLE)
set(OM_SANDBOX_SRC src/sandbox_darwin.c)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(OM_SANDBOX_SRC src/sandbox_linux.c)
else()
message(FATAL_ERROR
"Omicron: no hay sandbox para ${CMAKE_SYSTEM_NAME}. "
"Se aborta antes que generar un binario sin aislamiento real.")
endif()

# --- Binario -----------------------------------------------------------------
add_executable(omicron
src/main.c
src/policy.c
src/pty.c
${OM_SANDBOX_SRC}
)

# --- Tests -------------------------------------------------------------------
# tests/sandbox_null.c es un doble de test. Solo aparece en estos targets:
# el binario omicron no tiene forma de linkearlo.
enable_testing()

add_executable(test_policy tests/test_policy.c src/policy.c)
add_executable(test_pty tests/test_pty.c src/pty.c src/policy.c tests/sandbox_null.c)
add_executable(test_fdleak tests/test_fdleak.c src/pty.c src/policy.c tests/sandbox_null.c)
add_executable(test_sandbox tests/test_sandbox.c src/policy.c ${OM_SANDBOX_SRC})

foreach(t test_policy test_pty test_fdleak test_sandbox)
add_test(NAME ${t} COMMAND ${t})
# 77 = el test todavía es un stub; ctest lo reporta como Skipped, no como Passed.
set_tests_properties(${t} PROPERTIES SKIP_RETURN_CODE 77)
endforeach()
38 changes: 38 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Wrapper del flujo de CMake. Igual en macOS y en Linux.
# Uso: make | make test | make run ARGS="-- /bin/sh" | make clean

BUILD_DIR ?= build
BUILD_TYPE ?= Debug
CMAKE ?= cmake
CTEST ?= ctest
JOBS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)

.PHONY: all configure build test run clean rebuild help

all: build

configure: $(BUILD_DIR)/CMakeCache.txt

$(BUILD_DIR)/CMakeCache.txt:
$(CMAKE) -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE)
ln -sf $(BUILD_DIR)/compile_commands.json compile_commands.json

build: configure
$(CMAKE) --build $(BUILD_DIR) --parallel $(JOBS)

test: build
$(CTEST) --test-dir $(BUILD_DIR) --output-on-failure

run: build
./$(BUILD_DIR)/omicron $(ARGS)

clean:
rm -rf $(BUILD_DIR)

rebuild: clean build

help:
@echo "make compila"
@echo "make test compila y corre los tests"
@echo "make run ARGS=\"--allow-rw /tmp -- /bin/sh\""
@echo "make clean borra $(BUILD_DIR)/"
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,40 @@
# Terminal
# Omicron

Terminal Unix con sandbox: el proceso hijo se lanza con capacidades reducidas
por el kernel, no filtrando el texto del comando.

## Build

```
make # compila
make test # compila y corre los tests
make run ARGS="--allow-rw /tmp -- /bin/sh"
```

Requiere CMake >= 3.20 y un compilador C11. macOS y Linux.

## Estructura

```
include/omicron/ contrato: policy.h, sandbox.h, pty.h
src/ implementación (un sandbox por plataforma)
tests/ ctest
docs/adr/ decisiones arquitectónicas
```

## Reparto

| Archivo | Dueño |
|---|---|
| `src/main.c`, `src/policy.c`, `src/sandbox_darwin.c` | axel |
| `src/pty.c`, `src/sandbox_linux.c` | LevtCode |
| `include/`, `CMakeLists.txt` | compartidos: PR aprobado por ambos |
| `tests/test_*.c` | el dueño del módulo |

## Meta v1

```
omicron --allow-ro /usr --allow-rw /tmp -- /bin/sh
```

Adentro: `ls /tmp` funciona, `cat ~/.ssh/id_rsa` da `EPERM`.
Empty file added docs/adr/.gitkeep
Empty file.
41 changes: 41 additions & 0 deletions include/omicron/policy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* Política del sandbox: describe qué se le permite al proceso aislado.
* Son datos puros. No llama al kernel ni protege nada por sí sola:
* la traducen sandbox_darwin.c (Seatbelt) y sandbox_linux.c (Landlock). */
#ifndef OMICRON_POLICY_H
#define OMICRON_POLICY_H

#include <stdbool.h>
#include <stddef.h>
#include <sys/resource.h>

#ifndef OM_POLICY_FWD
#define OM_POLICY_FWD
typedef struct om_policy om_policy;
#endif

struct om_policy {
char **ro_paths; /* rutas de solo lectura */
size_t ro_count;
char **rw_paths; /* rutas de lectura y escritura */
size_t rw_count;
bool allow_net;
rlim_t max_procs;
rlim_t max_mem; /* bytes */
};

/* Política mínima razonable. NULL si falta memoria. */
om_policy *om_policy_default(void);

/* Agregan una ruta. 0 ok, -1 error (errno). La ruta se copia. */
int om_policy_add_ro(om_policy *p, const char *path);
int om_policy_add_rw(om_policy *p, const char *path);

/* Parsea flags hasta "--". Deja en *cmd_index el índice del comando en argv. */
int om_policy_parse_args(om_policy *p, int argc, char **argv, int *cmd_index);

/* Canonicaliza con realpath y rechaza lo inseguro. 0 ok, -1 rechazada. */
int om_policy_validate(om_policy *p);

void om_policy_free(om_policy *p);

#endif /* OMICRON_POLICY_H */
31 changes: 31 additions & 0 deletions include/omicron/pty.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* PTY: crea el par master/slave, lanza el hijo ya aislado y bombea E/S. */
#ifndef OMICRON_PTY_H
#define OMICRON_PTY_H

#include <sys/types.h>

#ifndef OM_POLICY_FWD
#define OM_POLICY_FWD
typedef struct om_policy om_policy;
#endif

/* Códigos de salida reservados del hijo: distinguen "no se ejecutó" de "falló". */
#define OM_EXIT_SANDBOX 127 /* el sandbox no se pudo aplicar; nunca hubo exec */
#define OM_EXIT_EXEC 126 /* el sandbox se aplicó pero el exec falló */

typedef struct {
int master_fd;
pid_t pid;
} om_pty;

/* fork + setsid + TIOCSCTTY + cerrar fds + om_sandbox_apply + execvp. */
int om_pty_spawn(char *const argv[], const om_policy *p, om_pty *t);

/* Bombea entre la terminal real y master_fd hasta que el hijo termine. */
int om_pty_pump(om_pty *t);

int om_pty_resize(om_pty *t, unsigned rows, unsigned cols);
int om_pty_wait(om_pty *t, int *status);
void om_pty_kill(om_pty *t);

#endif /* OMICRON_PTY_H */
23 changes: 23 additions & 0 deletions include/omicron/sandbox.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* Frontera de seguridad. Una firma, dos implementaciones (darwin / linux).
* Nada fuera de estos tres símbolos sabe qué mecanismo de kernel se usa. */
#ifndef OMICRON_SANDBOX_H
#define OMICRON_SANDBOX_H

#ifndef OM_POLICY_FWD
#define OM_POLICY_FWD
typedef struct om_policy om_policy;
#endif

/* Opaco: cada plataforma define su propio contenido en su .c */
typedef struct om_sandbox om_sandbox;

/* Corre en el PADRE, antes del fork. Puede reservar memoria. NULL si falla. */
om_sandbox *om_sandbox_prepare(const om_policy *p);

/* Corre en el HIJO, entre fork y exec. Irreversible. Sin malloc.
* 0 ok, -1 error: el hijo debe hacer _exit(OM_EXIT_SANDBOX). */
int om_sandbox_apply(const om_sandbox *s);

void om_sandbox_free(om_sandbox *s);

#endif /* OMICRON_SANDBOX_H */
13 changes: 13 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* Dueño: axel. Solo orquesta: policy -> spawn -> pump -> wait. */
#include "omicron/policy.h"
#include "omicron/pty.h"

#include <stdio.h>

int main(int argc, char **argv)
{
(void)argc;
(void)argv;
fputs("omicron: sin implementar\n", stderr);
return 1;
}
49 changes: 49 additions & 0 deletions src/policy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* Dueño: axel. Portable: no toca APIs de sandbox. */
#include "omicron/policy.h"

#include <errno.h>
#include <stdlib.h>

om_policy *om_policy_default(void)
{
errno = ENOSYS;
return NULL;
}

int om_policy_add_ro(om_policy *p, const char *path)
{
(void)p;
(void)path;
errno = ENOSYS;
return -1;
}

int om_policy_add_rw(om_policy *p, const char *path)
{
(void)p;
(void)path;
errno = ENOSYS;
return -1;
}

int om_policy_parse_args(om_policy *p, int argc, char **argv, int *cmd_index)
{
(void)p;
(void)argc;
(void)argv;
(void)cmd_index;
errno = ENOSYS;
return -1;
}

int om_policy_validate(om_policy *p)
{
(void)p;
errno = ENOSYS;
return -1;
}

void om_policy_free(om_policy *p)
{
(void)p;
}
45 changes: 45 additions & 0 deletions src/pty.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* Dueño: LevtCode. Portable: usar posix_openpt, no openpty (evita #ifdef). */
#include "omicron/pty.h"

#include "omicron/sandbox.h"

#include <errno.h>

int om_pty_spawn(char *const argv[], const om_policy *p, om_pty *t)
{
(void)argv;
(void)p;
(void)t;
/* Orden obligatorio en el hijo: cerrar fds ANTES de om_sandbox_apply. */
errno = ENOSYS;
return -1;
}

int om_pty_pump(om_pty *t)
{
(void)t;
errno = ENOSYS;
return -1;
}

int om_pty_resize(om_pty *t, unsigned rows, unsigned cols)
{
(void)t;
(void)rows;
(void)cols;
errno = ENOSYS;
return -1;
}

int om_pty_wait(om_pty *t, int *status)
{
(void)t;
(void)status;
errno = ENOSYS;
return -1;
}

void om_pty_kill(om_pty *t)
{
(void)t;
}
Loading
Loading