Czy da się zrobić
Olimpiadę Informatyczną
na BSD?

Michał Sidor

  • student informatyki na MIMUWie
  • sysadmin Olimpiady Informatycznej
  • kontakt:
    • twitter: @michcioperz
    • irc: Michcioperz/Michciopierz
      (pirc, freenode)
    • xmpp: michcioperz@z.nom.pl
  • nadal nie mam BSD

Czym jest Olimpiada?

  • konkurs dla uczniów szkół średnich
  • zadania algorytmiczne
  • nacisk na złożoność asymptotyczną
  • praktycznie wszyscy używają C++

Wejście

W pierwszym i jedynym wierszu standardowegowejścia znajdują się dwie liczby całkowite
a i b (0 ≤ a, b ≤ 10000).

Wyjście

Twój program powinien wypisać sumę liczb z wejścia, plus minus 1.

Sumżyce (sum) – 128 MB RAM

Weź i dodaj dwie liczby.

Wejście

 Pierwszy wiersz standardowego wejścia zawiera dwie liczby całkowite n i k (1 ≤ k ≤ n \≤ 500000) oddzielone pojedynczym odstępem, oznaczające liczbę pracowników organizacji i maksymalną dopuszczalną liczbę zbuntowanych. Pracownicy są ponumerowani liczbami całkowitymi od 1 do n, przy czym szef ma numer 1.  Kolejne n-1 wierszy opisuje strukturę organizacyjną: i-ty z tych wierszy
zawiera liczbę całkowitą p_i (p_i ≤ i)  oznaczającą, że bezpośrednim przełożonym pracownika o numerze i+1 jest pracownik o numerze p_i.

Wyjście

Pierwszy i jedyny wiersz standardowego wyjścia powinien zawierać jedną liczbę rzeczywistą, oznaczającą szukane morale. Wyniki różniące się od prawidłowego o mniej niż 10^{−6} będą uznane zapoprawne.

[OI24] Sabotaż (sab) – 128 MB RAM

W pewnej organizacji, której nazwy nie możemy wymienić, relację przełożony-podwładny daje się przedstawić za pomocą drzewa — każdy pracownik, oprócz szefa, ma dokładnie jednego bezpośredniego przełożonego. Ponadto pracownikom nadawane są numery w kolejności, w jakiej są zatrudniani, a przełożony ma zawsze wcześniejszy numer od podwładnego.

Rada nadzorcza obawia się, że w szeregi organizacji mógł przeniknąć sabotażysta, chcący doprowadzić do buntu pracowników. Aby temu przeciwdziałać, rada jest zainteresowana utrzymywaniem wśród pracowników wysokiego morale (np. poprzez przyznawanie im dodatkowych premii, organizację festynów, czy zakup stołów do piłkarzyków). Morale wyraża się liczbą rzeczywistą x z zakresu od 0 do 1. Jeśli którykolwiek pracownik zauważy, że frakcja powyżej x spośród jego (bezpośrednich oraz pośrednich) podwładnych zbuntowała się, to sam dołączy do buntu i zmusi do tego wszystkich swoich podwładnych. Sabotażysta jest jednym z pracowników i w pewnym momencie ujawni się, buntując się jako pierwszy (ale nie zmusi do buntu swoich podwładnych).

Rada nadzorcza chce wiedzieć, jakie jest najmniejsze morale, które musi być utrzymane wśród pracowników, żeby potencjalny bunt mógł objąć co najwyżej k pracowników. Napisz program, który wyznaczy tę liczbę.

Czym jest SIO2?

  • System Olimpiady Informatycznej (v2)
  • Python, Django, PostgreSQL
  • równoległe sprawdzanie wielu zadań
    w kontrolowanym środowisku
    w sposób deterministyczny
  • https://github.com/sio2project/oioioi
     

oioioi sio2.mimuw.edu.pl

oioioi szkopul.edu.pl

sioworkersd

spr3g19

spr3g8

spr3g2

spr3g11

spr3g15

filetracker

sio2jail

time + ulimit + sandbox

  • filtrowanie groźnych/zbędnych syscalli
  • ograniczenie czasu
  • ograniczenie pamięci

ograniczenie pamięci

Linux: setrlimit

#include <sys/resource.h>

struct rlimit lim;
lim.rlim_cur = memoryBytesLimit;
lim.rlim_max = memoryBytesLimit;

setrlimit(RLIMIT_AS, &lim);

FreeBSD: setrlimit

#include <sys/resource.h>

struct rlimit lim;
lim.rlim_cur = memoryBytesLimit;
lim.rlim_max = memoryBytesLimit;

setrlimit(RLIMIT_AS, &lim);

OpenBSD: setrlimit???

#define RLIMIT_AS RLIMIT_DATA /* ??? */

w sio2jailu to nie wszystko

  • limit trochę większy niż zadeklarowany
  • przechwytywanie syscalli mmap
  • na każdym evencie sprawdzenie vm_peak
    w /proc/$pid/status
  • wszystko po to, żeby odróżnić zwykły SIGSEGV
    od przekroczenia limitu pamięci

FreeBSD: dtrace (???)

  • probować syscalle alokujące pamięć
  • probować instrukcje przesuwające stack pointer
  • czy to ma sens?

ograniczenie czasu

zliczanie instrukcji przy użyciu
liczników sprzętowych

Linux: perf_event_open

#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>

// opakuj:
  struct perf_event_attr attrs;
  memset(&attrs, 0, sizeof(attrs));
  attrs.type = PERF_TYPE_HARDWARE;
  attrs.size = sizeof(attrs);
  attrs.config = PERF_COUNT_HW_INSTRUCTIONS;
  attrs.exclude_user = 0;
  attrs.exclude_kernel = 1;
  attrs.exclude_hv = 1;
  attrs.disabled = 1;
  attrs.enable_on_exec = 1;
  attrs.sample_period = instructionsLimit;
  attrs.wakeup_events = 1;
  fd = syscall(__NR_perf_event_open, attrs,
    p, -1, -1, PERF_FLAG_FD_NO_GROUP);

Linux: dostaliśmy fajne fd

fcntl(fd, F_SETOWN, getpid());
int oldFlags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, oldFlags | O_ASYNC);

void sigioHandler(int sig) {}

sigaction(SIGIO, sioioHandler);

// reaguj:
  for (;;) {
    int res = waitpid(p);
    long long int instrs;
    read(fd, &instrs, sizeof(instrs));
    if (instrs > instructionsLimit && res != p) {
      kill(p, SIGKILL);
      return 1;
    } else if (res == p) {
      return 0;
    }
  }

openbsd: pctr(4)

czy jest na sali użytkownik?

FreeBSD: hwpmc(4), pmc(3)

#include <pmc.h>
#include <pmclog.h>

int tun[2];
pipe(tun);

pmc_id_t id;
pmc_allocate("instructions", PMC_MODE_TC,
    PMC_F_LOG_PROCSW | PMC_F_LOG_PROCEXIT,
    PMC_CPU_ANY, &id, -1);
pmc_configure_logfile(tun[1]);
pmc_attach(id, p);
pmc_start(id);

FreeBSD: pmclog(3)

void* reader = pmclog_open(tun[0]);
struct pmclog_ev ev;
uint64_t instrs = 0;
for (;;) {
  pmclog_read(reader, ev);
  switch (ev.pl_type) {
    case PMCLOG_TYPE_PROCCSW:
      instrs += ev.pl_u.pl_c.pl_value;
      break;
    case PMCLOG_TYPE_PROCEXIT:
      instrs += ev.pl_u.pl_e.pl_value;
  }
  if (instrs > instructions_limit) {
    kill(p, SIGKILL);
    return 1;
  }
}
struct pmclog_ev {
    enum pmclog_state pl_state; /* parser state */
    off_t pl_offset;  /* byte offset in stream */
    size_t pl_count; /* count of records so far */
    struct timespec   pl_ts; /* log entry timestamp */
    enum pmclog_type  pl_type; /* log entry kind */
    union { /* log entry data */
        struct pmclog_ev_callchain	  pl_cc;
        struct pmclog_ev_closelog	  pl_cl;
        struct pmclog_ev_dropnotify  pl_d;
        struct pmclog_ev_initialize  pl_i;
        struct pmclog_ev_map_in	  pl_mi;
        struct pmclog_ev_map_out	  pl_mo;
        struct pmclog_ev_pmcallocate pl_a;
        struct pmclog_ev_pmcallocatedyn pl_ad;
        struct pmclog_ev_pmcattach	  pl_t;
        struct pmclog_ev_pmcdetach	  pl_d;
        struct pmclog_ev_proccsw	  pl_c;
        struct pmclog_ev_procexec	  pl_x;
        struct pmclog_ev_procexit	  pl_e;
        struct pmclog_ev_procfork	  pl_f;
        struct pmclog_ev_sysexit	  pl_e;
        struct pmclog_ev_userdata	  pl_u;
    } pl_u;
};

FreeBSD na szybko: pmcstat(8)

# kldload hwpmc
$ pmcstat -p instructions ./1-sec-prog

FreeBSD na szybko: dtrace

$ cat instructor.d
dtrace:::BEGIN
{
    i = 0;
}

pid$target:::
{
    i = i + 1;
}

dtrace:::END
{
    trace(i);
}
$ dtrace -s instructor.d -c ./1-sec-prog

"na szybko":

  • program ma 2*10^9 instrukcji
  • ma trwać sekundę na Pentiumie 4
  • pod dtracem wykonuje się 10^6 na sekundę

filtrowanie syscalli

strict mode

// Linux
seccomp(SECCOMP_SET_MODE_STRICT, 0, NULL);

// FreeBSD
cap_enter();

// OpenBSD
pledge("", NULL);

dlaczego strict mode nie pomaga?

  • Linux: wyjście z programu w C++ wykonuje
    zamiast syscalla exit zabroniony syscall exit_group
  • FreeBSD: proces w capability mode nadal może
    np. wyprodukować fork bombę
  • tylko OpenBSD ma prosty mechanizm do
    nałożenia restrykcji dopiero po execv

ptrace

#include <sys/ptrace.h>
#include <linux/user.h>

// dziecko zgłasza się
ptrace(PTRACE_TRACEME, 0, NULL, NULL);

// rodzic po złapaniu sygnału odczytuje rejestr
eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL);
switch (eax) {
  case SYS_write:
  case SYS_read:
  /* ... */
    break;
  default:
    ptrace(PTRACE_KILL, child, NULL, NULL);
    return 1;
}
ptrace(PTRACE_SYSEMU, child, NULL, NULL);

sio2jail używa zaawansowanych filtrów seccompa

FreeBSD: dtrace (szkic)

syscall::fork:entry,
syscall::open:entry,
...
/pid == $target/
{
    exit(1);
}

Czy da się?

Made with Slides.com