Twoim zadaniem jest napisanie kodu, który wycieka co najmniej jeden bajt pamięci w jak najmniejszej liczbie bajtów. Pamięć musi zostać wyciekła, a nie tylko przydzielona .
Wyciek pamięci to pamięć, którą program przydziela, ale traci możliwość dostępu, zanim będzie mogła poprawnie zwolnić pamięć. W przypadku większości języków wysokiego poziomu pamięć ta musi być przydzielona na stercie.
Przykładem w C ++ może być następujący program:
int main(){new int;}
Powoduje to utworzenie new int
stosu bez wskaźnika do niego. Ta pamięć jest natychmiast wyciekana, ponieważ nie mamy możliwości uzyskania do niej dostępu.
Oto, jak może wyglądać podsumowanie wycieku z Valgrind :
LEAK SUMMARY:
definitely lost: 4 bytes in 1 blocks
indirectly lost: 0 bytes in 0 blocks
possibly lost: 0 bytes in 0 blocks
still reachable: 0 bytes in 0 blocks
suppressed: 0 bytes in 0 blocks
Wiele języków ma debugger pamięci (taki jak Valgrind ), jeśli możesz, powinieneś dołączyć dane wyjściowe z takiego debuggera, aby potwierdzić, że wyciekła pamięć.
Celem jest zminimalizowanie liczby bajtów w źródle.
Odpowiedzi:
Perl (5.22.2), 0 bajtów
Wypróbuj online!
Wiedziałem, że będzie jakiś język, który wyciekł pamięć z pustego programu. Spodziewałem się, że będzie to esolang, ale okazuje się, że
perl
przecieka pamięć w dowolnym programie. (Zakładam, że jest to celowe, ponieważ zwolnienie pamięci, jeśli wiesz, że i tak zamierzasz wyjść, to po prostu marnuje czas; w związku z tym obecnie powszechnym zaleceniem jest po prostu wyciekanie pozostałej pamięci, gdy znajdziesz się w procedurach wyjścia programu .)Weryfikacja
źródło
perl --version
na mojej maszynie , mimo że nigdy nie uruchamia żadnego programu.C,
48 3122 bajtówOstrzeżenie: nie uruchamiaj tego zbyt wiele razy.
Podziękowania dla Dennisa za wiele pomocy / pomysłów!
To idzie o krok dalej.
shmget
przydziela pamięć współdzieloną, która nie jest zwalniana po zakończeniu programu. Używa klucza do identyfikacji pamięci, więc używamy niezainicjowanej int. Jest to technicznie niezdefiniowane zachowanie, ale praktycznie oznacza to, że używamy wartości, która znajduje się tuż nad szczytem stosu, gdy jest ona wywoływana. Zostanie to zapisane przy następnym dodaniu czegokolwiek do stosu, więc stracimy klucz.Jedynym przypadkiem, że to nie działa, jest to, że możesz dowiedzieć się, co było wcześniej na stosie. W przypadku dodatkowych 19 bajtów można uniknąć tego problemu:
Lub dla 26 bajtów:
Ale w tym przypadku pamięć wycieka po wyjściu z programu. Podczas działania program ma dostęp do pamięci, co jest niezgodne z regułami, ale po zakończeniu programu tracimy dostęp do klucza i pamięć jest nadal przydzielana. Wymaga to randomizacji układu przestrzeni adresowej (ASLR), w przeciwnym razie
&k
zawsze będzie to samo. Obecnie ASLR jest zazwyczaj domyślnie włączony.Weryfikacja:
Możesz użyć,
ipcs -m
aby zobaczyć, jaka pamięć współdzielona istnieje w twoim systemie. Dla przejrzystości usunąłem wcześniej istniejące wpisy:źródło
Unlambda (
c-refcnt/unlambda
), 1 bajtWypróbuj online!
To naprawdę wyzwanie, aby znaleźć istniejącego interpretera, który przecieka pamięć w bardzo prostych programach. W tym przypadku użyłem Unlambda. Istnieje więcej niż jeden oficjalny interpreter Unlambda, ale
c-refcnt
jest to jeden z najłatwiejszych do zbudowania i ma tę użyteczną właściwość, że przecieka pamięć po pomyślnym uruchomieniu programu. Więc wszystko, co musiałem tu podać, to najprostszy możliwy legalny program Unlambda, brak możliwości. (Zwróć uwagę, że pusty program tutaj nie działa; pamięć jest nadal dostępna w momencie awarii tłumacza).Weryfikacja
źródło
TI-Basic, 12 bajtów
„... przeciek pamięci to miejsce, w którym używasz Goto / Lbl w pętli lub warunkowo (cokolwiek, co ma polecenie End), aby wyskoczyć z tej struktury kontrolnej przed osiągnięciem polecenia End ...” (więcej)
źródło
Pause
na końcu? Możesz zapisać 2 bajty.Python <3.6.5, 23 bajty
property.__init__
przecieki odniesienia do nieruchomości jest staryfget
,fset
,fdel
, i__doc__
jeśli nazywają go już przygotowanejproperty
instancji. Jest to błąd, który ostatecznie został zgłoszony jako część problemu CPython 31787 i naprawiony w Python 3.6.5 i Python 3.7.0 . (Tak,property([])
można to zrobić.)źródło
C #, 34 bajty
To rozwiązanie nie wymaga sterty. Po prostu potrzebuje naprawdę ciężko pracującego GC ( Garbage Collector ).
Zasadniczo zmienia GC we własnego wroga.
Wyjaśnienie
Za każdym razem, gdy wywoływany jest destruktor , tworzy nowe instancje tej klasy zła, o ile upłynie limit czasu i każe GC po prostu porzucić ten obiekt, nie czekając na zakończenie destruktora. Do tego czasu powstały tysiące nowych instancji.
„Zło” tego polega na tym, że im trudniej działa GC, tym bardziej będzie to wybuchało na twojej twarzy.
Oświadczenie : Twój GC może być mądrzejszy od mojego. Inne okoliczności w programie mogą spowodować, że GC zignoruje pierwszy obiekt lub jego destruktor. W takich przypadkach nie wybuchnie. Ale w wielu odmianach tak będzie . Dodanie kilku bajtów tutaj i może zapewnić wyciek w każdych możliwych okolicznościach. Może poza wyłącznikiem zasilania.
Test
Oto pakiet testowy :
Wyjście po 10 minutach:
To 2 684 476 624 bajtów. Cały
WorkingSet
proces wynosił około 4,8 GBTa odpowiedź została zainspirowana cudownym artykułem Erica Lipperta: Kiedy wszystko, co wiesz, jest złe .
źródło
class L{~L(){new L();}}
? AFAIKfor(;;)
sprawia, że pamięć przecieków jest szybsza, prawda?C (gcc) , 15 bajtów
Weryfikacja
źródło
JavaScript, 14 bajtów
Grał w golfa
Rejestruje pustą procedurę obsługi przedziałów z domyślnym opóźnieniem, odrzucając wynikowy identyfikator timera (uniemożliwiając anulowanie).
Użyłem innego niż domyślny interwału, aby stworzyć kilka milionów timerów, aby zilustrować wyciek, ponieważ użycie domyślnego interwału zjada procesor jak szalony.
źródło
if(window && window.setInterval && typeof window.setInterval === 'function') { window.setInterval(0); }
clearInterval
z rosnącym identyfikatorem, dopóki nie minie interwał. Na przykład:for(let i=0;i<1e5;i++){try{clearInterval(i);}catch(ex){}}
free()
Java, 10 bajtów
Wreszcie konkurencyjna odpowiedź w Javie!
Grał w golfa
To jest odwołanie do metody (w stosunku do stałej ciągu), którego można użyć w następujący sposób:
Ciągiem znaków
". "
zostanie automatycznie dodany do światowej internowany strings basenie, jak prowadzony przezjava.lang.String
klasy, a jak immediatelly przyciąć go, odniesienie do niego nie mogą być ponownie wykorzystane w dalszej części kodu (chyba, że deklarują dokładnie ten sam ciąg ponownie).https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#intern--
Możesz to zmienić w wyciek pamięci „na poziomie produkcyjnym”, dodając ciąg do siebie, a następnie jawnie wywołując metodę intern () w pętli.
źródło
("." + " ").intern()
by zrobił (gdyby były one wprowadzane przez użytkownika lub w / e, więc pomijamy optymalizacje kompilatora).simply by guessing it correctly
, ale to nie znaczy, że nie ma czegoś takiego jak wycieki pamięci.there's probably some way to use reflection to determine the string's contents too
mógłbyś to zademonstrować? (wskazówka: String.intern () jest zaimplementowany w natywnym kodzie).Rdza, 52 bajty
Przydziela niektóre bajty za pomocą systemowego malloc. Zakłada się, że niewłaściwy ABI jest dopuszczalny.
Rdza (teoretycznie), 38 bajtówPrzydzielamy pamięć na stercie, wyodrębniamy surowy wskaźnik, a następnie po prostu go ignorujemy, skutecznie przeciekając. (
Box::into_raw
jest wtedy krótszystd::mem::forget
).Jednak Rust domyślnie używa jemalloc, którego valgrind nie może wykryć żadnego wycieku . Moglibyśmy przejść do alokatora systemu, ale to dodaje 50 bajtów i wymaga co noc. Wielkie dzięki za bezpieczeństwo pamięci.
Wyjście pierwszego programu:
źródło
8086 ASM, 3 bajty
W tych przykładach założono, że środowisko wykonawcze C jest połączone.
łączy się z tym,
e9 XX XX
gdzieXX XX
jest względny adres_malloc
Powoduje
malloc
to przydzielenie nieprzewidzianej ilości pamięci, a następnie natychmiast powraca, kończąc procesy. W niektórych systemach operacyjnych, takich jak DOS, pamięć może w ogóle nie zostać odzyskana, dopóki system nie zostanie ponownie uruchomiony!źródło
Dalej, 6 bajtów
Grał w golfa
Przydziela pusty ciąg znaków
s" "
, pozostawiając jego stos i długość (0) na stosie, a następnie mnoży je (powodując utratę adresu pamięci).Valgrind
źródło
przejść 45 bajtów
tworzy to anonimowy goroutine z nieskończoną pętlą w nim. program może nadal działać normalnie, ponieważ uruchomienie goroutine przypomina trochę odrodzenie równolegle działającego małego wątku, ale program nie ma możliwości odzyskania pamięci przydzielonej dla goroutine. śmieciarz też go nie odbierze, ponieważ nadal działa. niektórzy nazywają to „wyciekiem goroutine”
źródło
C.malloc(8)
, ponieważ musiszimport"C"
Java 1.3, 23 bajty
Tworzenie wątku, ale nie uruchamianie go. Wątek jest zarejestrowany w wewnętrznej puli wątków, ale nigdy nie zostanie uruchomiony, więc nigdy się nie zakończył, a zatem nigdy nie będzie kandydatem do GC. Jest to obiekt niemożliwy do odzyskania, utknął w limbos Java.
Jest to błąd w Javie do wersji 1.3, ponieważ został później naprawiony.
Testowanie
Poniższy program upewnia się, że pamięć zostanie zanieczyszczona nowymi obiektami wątku i pokaże zmniejszające się wolne miejsce w pamięci. Ze względu na testy szczelności intensywnie uruchamiam GC.
źródło
Befunge ( grzyby ), 1 bajt
Może to zależeć od platformy i wersji (testowałem tylko z wersją 1.0.4 w systemie Windows), ale grzyby w przeszłości były bardzo nieszczelnym interpretatorem. Polecenie
$
(drop) nie powinno robić nic na pustym stosie, ale zapętlenie tego kodu w jakiś sposób bardzo szybko powoduje wyciek dużej ilości pamięci. W ciągu kilku sekund zużyje kilka koncertów i zawiesi się z błędem „brak pamięci”.Zauważ, że niekoniecznie musi to być
$
polecenie - prawie wszystko by zrobił. Jednak nie będzie działać z pustym plikiem źródłowym. Musi być co najmniej jedna operacja.źródło
Szybkie 3, 38 bajtów
Nowa wersja:
x
ma silne odniesienie do siebie, więc nie zostanie zwolniony, co spowoduje wyciek pamięci.Stara wersja:
x
zawiera silne odniesienie doy
i na odwrót. Zatem żadne z nich nie zostanie zwolnione, co spowoduje wyciek pamięci.źródło
x
iy
, więc tak naprawdę nie wygląda to na wyciek (chyba, że jakoś ją zniszczysz).do
bloku, który rozwiązałby problem podniesiony przez Zeppelina, prawda?do
by działało. Dobry pomysł!class X{var x: X!};do{let x=X();x.x=x}
Delphi (Object Pascal) - 33 bajty
Tworzenie obiektu bez zmiennej, pełnej konsoli programu:
Włączenie FastMM4 w projekcie pokaże wyciek pamięci:
źródło
C # - 84 bajtów
Przydziela dokładnie 1 bajt niezarządzanej pamięci, a następnie traci
IntPtr
, co, jak sądzę, jest jedynym sposobem na uzyskanie lub uwolnienie jej. Możesz to przetestować, umieszczając go w pętli i czekając na awarię aplikacji (możesz dodać kilka zer, aby przyspieszyć działanie).Uważałem
System.IO.File.Create("a");
i takie, ale nie jestem przekonany, że są to koniecznie wycieki pamięci, a sama aplikacja będzie zbierać pamięć, to system operacyjny pod który może wyciekać (boClose
alboDispose
nie były nazywane). Dostęp do plików wymaga również uprawnień systemu plików i nikt nie chce na nich polegać. I okazuje się, że i tak nie wycieknie, ponieważ nic nie stoi na przeszkodzie, aby wywołać finalizator (co uwalnia możliwe zasoby podstawowe), co obejmuje ramy w celu złagodzenia tego rodzaju błędów w ocenie (do pewnego stopnia), oraz pomylić programistów z pozornie niedeterministycznym blokowaniem plików (jeśli jesteś cynikiem). Podziękowania dla Jona Hanny za postawienie mnie w tej sprawie.Jestem trochę rozczarowany, że nie mogę znaleźć krótszej drogi. .NET GC działa, nie mogę wymyślić żadnego
IDisposables
w mscorlib, który na pewno wyciekłby (i rzeczywiście wszyscy wydają się mieć finalizatory, jak denerwujące) , nie znam żadnego innego sposobu przydzielania niezarządzanej pamięci (oprócz PInvoke ), a odbicie gwarantuje niczego z odniesieniem do niego (bez semantyki języka (np prywatnych członków lub klas z żadnym dostępowych)) może zostać znaleziony.źródło
System.IO.File.Create("a")
nic nie wycieknie, aleGC.SuppressFinalize(System.IO.File.Create("a"))
zrobi to, ponieważ jest wyraźnie proszone, aby nie uruchamiać finalizatoraFileStream
wyprodukowanego.<!-- language: lang-c# -->
Dzięki za tę i miłą odpowiedź! (To C #, więc uwielbiam to)Współczynnik , 13 bajtów
Factor ma automatyczne zarządzanie pamięcią, ale daje także dostęp do niektórych funkcji libc:
Ręcznie przydziela 1 bajt pamięci, zwraca jej adres i upuszcza.
malloc
faktycznie rejestruje kopię, aby śledzić wycieki pamięci i podwójnie zwalnia, ale zidentyfikowanie tego, co wyciekłeś, nie jest łatwym zadaniem.Jeśli wolisz się upewnić, że naprawdę zgubiłeś to odniesienie:
Testowanie wycieków za pomocą
[ 1 malloc drop ] leaks.
mówi:Testowanie wycieków za pomocą
[ 1 (malloc) drop ] leaks.
mówi:O nie! Zły czynnik, teraz ma Alzheimera! RE:
źródło
Common Lisp (tylko SBCL),
2826 bajtówUruchamiasz to tak
sbcl --eval 'sb-alien::(make-alien int)'
:; nic nie jest drukowane ani zwracane, ale następuje przydzielenie pamięci. Jeśli owinę formularz w a(print ...)
, wskaźnik zostanie wyświetlony w REPL.package::(form)
jest specjalnym zapisem w SBCL do tymczasowego wiązania bieżącego pakietu podczas czytania formularza. Służy to tutaj, aby uniknąć prefiksu zarówno z, jakmake-alien
iz . Myślę, że byłoby oszustwo, zakładając, że bieżący pakiet jest ustawiony na ten, ponieważ tak nie jest podczas uruchamiania.int
sb-alien
make-alien
przydziela pamięć dla danego typu i opcjonalnego rozmiaru (przy użyciu malloc).Wykonując to w REPL, dodaj
0
po alokacji, aby REPL nie zwracał wskaźnika, ale tę wartość. W przeciwnym razie, że nie będzie to prawdziwy przeciek ponieważ REPL pamięta ostatnie trzy zwrócone wartości (patrz*
,**
,***
) i wciąż może mieć szansę zwolnić przydzieloną pamięć.2 bajty usunięte dzięki PrzemysławP, dzięki!
źródło
1
(lub2
,3
etc.), a nie()
tak, że wartość zwracana1
? Pozwoliłoby to zaoszczędzić 1 bajt. Czy ta odpowiedź jest tylko REPL? Może jeśli załadujesz kodload
, nie możesz go dołączyć()
ani nic na końcu, ponieważ i tak nie będzie dostępny?eval
i działa to tak, jak powiedziałeś. Wielkie dzięki!AutoIt , 39 bajtów
Przydziela jeden bajt ze sterty. Ponieważ zwracany uchwyt
_MemGlobalAlloc
jest odrzucany, nie ma możliwości jawnego zwolnienia tego przydziału.źródło
C ++, 16 bajtów
Nie mam Valgrinda, aby sprawdzić, czy nie ma wycieków, ale jestem pewien, że powinien.W przeciwnym razie spróbowałbym:Wynik Valgrind
(Rzeczywiście przecieka)
źródło
g++ 4.3.2
(nie najnowszy) i kompiluje się dobrze.int
Domyślnie żaden typ zwrotu nie jest . Ze-Wall
mam ostrzeżenia:plop.cpp:1: warning: ISO C++ forbids declaration of 'main' with no type
[]{new int;}
jako funkcję C ++ (wyzwanie nie określiło całego programu).Java (OpenJDK 9) ,
322220 bajtówWypróbuj online!
Jest to inny wyciek pamięci, który nie korzysta z pamięci podręcznej ciągów. Przydziela połowę pamięci RAM i nie możesz nic z tym zrobić.
Dzięki Zeppelin za uratowanie wszystkich bajtów
źródło
Unsafe
instancję ze zmiennej statycznej w jej wnętrzu, w ten sposób:import sun.misc.*;class M{static void main(String[]a)throws Exception{java.lang.reflect.Field f=Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(1<2);((Unsafe)f.get(1)).allocateMemory(1);}}
public static void main
statyczny inicjatorstatic{try{}catch(Exception e){}}
(który może być nieco trudniejszy do uruchomienia, ale mimo to jest prawidłowy i kompilowalny).a
zamiastargs
i usuń publiczne. tio.run/nexus/…c, 9 bajtów
Dowód:
źródło
gcc
jest. Powinno to również działać z pustym programem. Spróbujgcc src.c && valgrind ./a.out
, co powinno dać czysty wynik.C #, 109 bajtów
Znaleźliśmy ideę tego wycieku w kodzie produkcyjnym i badanie tego prowadzi do tego artykułu. Głównym problemem jest długi cytat z artykułu (przeczytaj go, aby uzyskać więcej informacji):
Uruchomienie z kompilatora w Visual Studio 2015 i użycie okna narzędzi diagnostycznych pokazuje następujące wyniki po około 38 sekundach. Zauważ, że pamięć procesu stale się powiększa, a Garbage Collector (GC) działa nadal, ale nie może niczego zbierać.
źródło
C 30 bajtów
Wyniki Valgrind:
źródło
main(){malloc(1);}
?Dart, 76 bajtów
Trochę jak odpowiedź JavaScript. Kiedy wywołujesz
.listen
obiekt strumienia Dart, otrzymujesz zwrot StreamSubscription, który pozwala ci rozłączyć się ze strumieniem. Jednak jeśli go wyrzucisz, nigdy nie możesz anulować subskrypcji strumienia, co spowoduje wyciek. Jedynym sposobem na usunięcie wycieku jest zebranie samego strumienia, ale nadal jest on wewnętrznie wywoływany przez kombinację StreamController + Timer.Niestety, Dart jest zbyt mądry na inne rzeczy, których próbowałem.
()async=>await new Completer().future
nie działa, ponieważ użycie funkcji Oczekiwanie jest tym samym, co czynnośćnew Completer().future.then(<continuation>)
, co pozwala zniszczyć samo zamknięcie, a do drugiego Completera nie ma odniesienia (Completer zawiera odniesienie do Przyszłości od.future
, Future zawiera odniesienie do kontynuacji jako zamknięcie).Ponadto izolaty (znane również jako wątki) są usuwane przez GC, więc odrodzenie się w nowym wątku i natychmiastowe wstrzymanie go (
import'dart:isolate';main(_)=>Isolate.spawn(main,0,paused:true);
) nie działa. Nawet spawnowanie Izolatu z nieskończoną pętlą (import'dart:isolate';f(_){while(true){print('x');}}main()=>Isolate.spawn(f,0);
) zabija Izolację i wychodzi z programu.No cóż.
źródło
Szybki, 12 bajtów
Wyjaśnienie:
Jest to de facto wyciek pamięci, który może wystąpić w dowolnym języku, niezależnie od tego, czy język korzysta z ręcznego zarządzania pamięcią, automatycznego liczenia referencji (ARC, jak Swift), a nawet zamiatania śmieci.
[3,5]
jest dosłownie tablicą. Ta tablica przydziela wystarczającą ilość pamięci dla co najmniej tych 2 elementów.3
I5
to tylko arbitralne.Indeksowanie (indeksowanie)
Array<T>
tworzyArraySlice<T>
. AnArraySlice<T>
jest widokiem pamięci Array, z której został utworzony.[3,5][0...0]
tworzy wartośćArraySlice<Int>
, której wartość wynosi[3]
. Zauważ, że3
w tym wycinku jest ten sam3
element, co3
w oryginaleArray
pokazanym powyżej, a nie kopia.Powstały wycinek można następnie zapisać w zmiennej i wykorzystać. Oryginalna tablica nie jest już przywoływana, więc można by pomyśleć, że można ją zwolnić. Jednak nie może.
Ponieważ wycinek odsłania widok pamięci tablicy, z której pochodzi, pierwotna tablica musi być utrzymywana przy życiu tak długo, jak plasterek żyje. Tak więc z pierwotnej
2
wielkości pamięci o wielkości elementu, która została przydzielona, używana jest tylko pamięć o wartości pierwszego rozmiaru elementu, przy czym drugi musi istnieć, aby nie przydzielić pierwszego. Rozmiar drugiego elementu pamięci jest przeciekany.Rozwiązaniem tego problemu jest nie utrzymywanie przy życiu małych plasterków dużych tablic. Jeśli chcesz zachować zawartość wycinka, wypromuj ją w tablicy, która uruchomi pamięć do skopiowania, usuwając w ten sposób zależność od pamięci oryginalnej tablicy:
źródło
Rozwiązanie 1: C (Mac OS X x86_64), 109 bajtów
Źródło dla golf_sol1.c
Powyższy program należy skompilować z dostępem do wykonania w segmencie __DATA.
Następnie, aby uruchomić program, uruchom następujące czynności:
Wyniki:
Niestety Valgrind nie szuka pamięci przydzielonej z wywołań systemowych, więc nie mogę pokazać ładnie wykrytego wycieku.
Możemy jednak spojrzeć na vmmap, aby zobaczyć dużą część przydzielonej pamięci (metadane MALLOC).
Wyjaśnienie
Myślę więc, że muszę opisać, co się tutaj właściwie dzieje, zanim przejdę do ulepszonego rozwiązania.
Ta główna funkcja nadużywa deklaracji brakującego typu C (więc domyślnie int, bez marnowania znaków podczas pisania), a także jak działają symbole. Linker dba tylko o to, czy nie może znaleźć symbolu,
main
do którego należy zadzwonić. Więc tutaj tworzymy główną tablicę liczb całkowitych, które inicjujemy za pomocą naszego kodu powłoki, który zostanie wykonany. Z tego powodu main nie zostanie dodany do segmentu __TEXT, ale raczej do segmentu __DATA, dlatego musimy skompilować program z wykonywalnym segmentem __DATA.Znaleziony w main kod powłoki jest następujący:
Wywołuje to funkcję syscall w celu przydzielenia strony pamięci (syscall mach_vm_allocate używa wewnętrznie). RAX powinien być równy 0x100000a (mówi syscall, której funkcji chcemy), podczas gdy RDI utrzymuje cel alokacji (w naszym przypadku chcemy, aby to był mach_task_self ()), RSI powinien przechowywać adres, aby zapisać wskaźnik do nowo utworzonej pamięci (więc wskazujemy tylko sekcję na stosie), RDX zachowuje rozmiar alokacji (przekazujemy tylko RAX lub 0x100000a, aby zaoszczędzić na bajtach), R10 przechowuje flagi (wskazujemy, że może być przydzielone w dowolnym miejscu).
Teraz nie jest oczywiste, skąd RAX i RDI czerpią swoje wartości. Wiemy, że RAX musi mieć wartość 0x100000a, a RDI musi być wartością zwracaną przez mach_task_self (). Na szczęście mach_task_self () jest w rzeczywistości makrem zmiennej (mach_task_self_), która ma ten sam adres pamięci za każdym razem (jednak powinna się zmienić przy ponownym uruchomieniu). W moim szczególnym przypadku mach_task_self_ znajduje się pod adresem 0x00007fff7d578244. Aby ograniczyć instrukcje, zamiast tego przekażemy te dane z argv. Dlatego uruchamiamy program z tym wyrażeniem
$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
za pierwszy argument. Łańcuch to dwie połączone wartości, w których wartość RAX (0x100000a) wynosi tylko 32 bity i zastosowano do niej dopełnienie jednego (więc nie ma bajtów zerowych; po prostu NIE mamy wartości, aby uzyskać oryginał), następna wartość to RDI (0x00007fff7d578244), który został przesunięty w lewo z 2 dodatkowymi śmieciami dodanymi na końcu (ponownie, aby wykluczyć bajty puste, po prostu przesuwamy go z powrotem w prawo, aby przywrócić go do pierwotnego).Po syscall piszemy do naszej nowo przydzielonej pamięci. Powodem tego jest to, że pamięć przydzielana za pomocą mach_vm_allocate (lub tego syscall) to w rzeczywistości strony VM i nie są automatycznie przenoszone do pamięci. Są one raczej zarezerwowane, dopóki dane nie zostaną do nich zapisane, a następnie strony te zostaną zmapowane w pamięci. Nie byłem pewien, czy spełniłby wymagania, gdyby był zarezerwowany.
W kolejnym rozwiązaniu wykorzystamy fakt, że nasz kod powłoki nie ma zerowych bajtów, więc możemy przenieść go poza kod naszego programu, aby zmniejszyć rozmiar.
Rozwiązanie 2: C (Mac OS X x86_64), 44 bajty
Źródło dla golf_sol2.c
Powyższy program należy skompilować z dostępem do wykonania w segmencie __DATA.
Następnie, aby uruchomić program, uruchom następujące czynności:
Wynik powinien być taki sam jak poprzednio, ponieważ dokonujemy alokacji o tym samym rozmiarze.
Wyjaśnienie
Stosuje tę samą koncepcję, co rozwiązanie 1, z tym wyjątkiem, że przenieśliśmy część naszego wycieku kodu poza program.
Kod powłoki znaleziony w main jest teraz następujący:
To w zasadzie kopiuje kod powłoki, który przekazujemy w argv po tym kodzie (więc po skopiowaniu go uruchomi wstawiony kod powłoki). Na naszą korzyść działa to, że segment __DATA będzie miał co najmniej rozmiar strony, więc nawet jeśli nasz kod nie jest tak duży, nadal możemy „bezpiecznie” pisać więcej. Minusem jest tutaj idealne rozwiązanie, nawet nie potrzebuje kopii, zamiast tego po prostu wywołuje i wykonuje kod powłoki bezpośrednio w argv. Ale niestety ta pamięć nie ma uprawnień do wykonywania. Moglibyśmy zmienić prawa do tej pamięci, jednak wymagałoby to więcej kodu niż zwykłe kopiowanie. Alternatywną strategią byłaby zmiana praw z programu zewnętrznego (ale o tym później).
Kod powłoki, który przekazujemy argv, jest następujący:
Jest to bardzo podobne do naszego poprzedniego kodu, z tą różnicą, że uwzględniamy bezpośrednio wartości EAX i RDI.
Możliwe rozwiązanie 1: C (Mac OS X x86_64), 11 bajtów
Pomysł zmodyfikowania programu zewnętrznie daje nam możliwe rozwiązanie przeniesienia przecieku do programu zewnętrznego. Tam, gdzie nasz rzeczywisty program (przesłanie) jest tylko programem zastępczym, a program nieszczelny przydzieli część pamięci w naszym programie docelowym. Teraz nie byłem pewien, czy będzie to zgodne z zasadami tego wyzwania, ale mimo to podzielam się nim.
Więc jeśli użyjemy mach_vm_allocate w zewnętrznym programie z celem ustawionym na nasz program wyzwań, może to oznaczać, że nasz program wyzwań musiałby być czymś w rodzaju:
Tam, gdzie ten kod powłoki to po prostu krótki skok do siebie (nieskończony skok / pętla), więc program pozostaje otwarty i możemy odwoływać się do niego z zewnętrznego programu.
Możliwe rozwiązanie 2: C (Mac OS X x86_64), 8 bajtów
Co zabawne, kiedy patrzyłem na produkcję valgrind, zobaczyłem, że przynajmniej według valgrind, słaba pamięć przecieków. Tak skutecznie każdy program przecieka trochę pamięci. Mając to na uwadze, moglibyśmy po prostu stworzyć program, który nic nie robi (po prostu wychodzi) i faktycznie wycieknie pamięć.
Źródło:
źródło
Zwykły angielski ,
71705835 bajtówUsunięto 1 bajt, usuwając pusty wiersz. Usunięto 12 bajtów poprzez wyeliminowanie definicji typu „bogon” i użycie nadrzędnego typu „rzecz” zamiast podtypu „bogon”. Usunięto 23 bajty, przechodząc z kompletnego programu do zwykłej procedury, która przecieka pamięć.
Wersja golfowa:
Wersja bez golfa, która jest kompletnym programem, używa definicji podtypu i nie przecieka pamięci:
Jeśli zostanie wywołana wersja „x” gry w golfa, nastąpi wyciek pamięci proporcjonalnie do liczby wywołań „x”. W wersji golfowej „Cofnij przydzielenie rzeczy”. naprawiłby wyciek pamięci.
Zwykły angielski domyślnie sprawdza, czy nie ma wycieków pamięci. Po uruchomieniu wersji, która przecieka pamięć, tuż przed zamknięciem programu pojawi się okno dialogowe. Okno dialogowe ma tytuł „debugowanie”, komunikat „1 kroplówka” i przycisk „OK”. Im więcej razy wywoływana jest funkcja przecieku, tym większa liczba „kropelek” w komunikacie. Po uruchomieniu wersji, która nie przecieka pamięci, okno dialogowe nie pojawia się.
W zwykłym języku angielskim „rzecz” to wskaźnik do pozycji na liście podwójnie połączonej. „Rzecz”, „uruchomić” i „zamknąć” są zdefiniowane w module o nazwie „makaron”, który należy skopiować (zwykle jako osobny plik) do każdego projektu. „A”, „the”, „to”, „przydzielić pamięć” i „zniszczyć” są zdefiniowane w kompilatorze.
źródło