Zastosowanie /proc/self/exe
jest nieprzenośne i zawodne. W moim systemie Ubuntu 12.04 musisz być rootem, aby czytać / podążać za dowiązaniem symbolicznym. To sprawi, że Boost będzie przykładem i prawdopodobnie whereami()
zamieszczone rozwiązania zawiodą.
Ten post jest bardzo długi, ale omawia aktualne problemy i przedstawia kod, który faktycznie działa wraz z sprawdzaniem poprawności względem zestawu testów.
Najlepszym sposobem na znalezienie programu jest odtworzenie tych samych kroków, które wykonuje system. Odbywa się to za pomocą argv[0]
rozwiązania rozwiązanego względem systemu plików root, pwd, środowiska ścieżki i rozważenia dowiązań symbolicznych oraz kanonizacji nazw ścieżek. To pochodzi z pamięci, ale robiłem to z powodzeniem w przeszłości i testowałem w różnych sytuacjach. Nie gwarantuje się, że zadziała, ale jeśli nie, prawdopodobnie masz znacznie większe problemy i jest ogólnie bardziej niezawodny niż jakakolwiek inna omawiana metoda. Istnieją sytuacje w systemie kompatybilnym z Uniksem, w których właściwe obchodzenie się zargv[0]
nie spowoduje przejścia do twojego programu, ale wtedy wykonujesz w certyfikowanym środowisku. Jest także dość przenośny dla wszystkich systemów pochodnych Uniksa od około 1970 roku, a nawet niektórych systemów nie pochodzących z Uniksa, ponieważ zasadniczo opiera się na standardowej funkcjonalności libc () i standardowej linii poleceń. Powinien działać na systemie Linux (wszystkie wersje), Android, Chrome OS, Minix, oryginalny Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, Następny krok itp. I przy niewielkiej modyfikacji prawdopodobnie VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2 itd. Jeśli program został uruchomiony bezpośrednio ze środowiska GUI, powinien był ustawić argv[0]
ścieżkę bezwzględną.
Zrozum, że prawie każda powłoka w każdym systemie operacyjnym kompatybilnym z Uniksem, który kiedykolwiek został wydany, zasadniczo znajduje programy w ten sam sposób i konfiguruje środowisko operacyjne prawie w ten sam sposób (z pewnymi opcjonalnymi dodatkami). Oczekuje się, że każdy inny program, który uruchamia program, utworzy dla tego programu takie samo środowisko (argv, ciągi środowiska itp.), Jakby było uruchamiane z powłoki, z pewnymi opcjonalnymi dodatkami. Program lub użytkownik może skonfigurować środowisko, które odbiega od tej konwencji, dla innych programów podrzędnych, które uruchamia, ale jeśli tak, jest to błąd i program nie ma uzasadnionych oczekiwań, że program podrzędny lub jego podwładni będą działać poprawnie.
Możliwe wartości argv[0]
obejmują:
/path/to/executable
- ścieżka bezwzględna
../bin/executable
- w stosunku do pwd
bin/executable
- w stosunku do pwd
./foo
- w stosunku do pwd
executable
- basename, znajdź na ścieżce
bin//executable
- w stosunku do pwd, niekanoniczne
src/../bin/executable
- w stosunku do pwd, niekanonicznego, cofania
bin/./echoargc
- w stosunku do pwd, niekanoniczne
Wartości, których nie powinieneś widzieć:
~/bin/executable
- przepisane przed uruchomieniem programu.
~user/bin/executable
- przepisane przed uruchomieniem programu
alias
- przepisane przed uruchomieniem programu
$shellvariable
- przepisane przed uruchomieniem programu
*foo*
- symbol wieloznaczny, przepisany przed uruchomieniem programu, niezbyt przydatny
?foo?
- symbol wieloznaczny, przepisany przed uruchomieniem programu, niezbyt przydatny
Ponadto mogą one zawierać niekanoniczne nazwy ścieżek i wiele warstw dowiązań symbolicznych. W niektórych przypadkach może istnieć wiele twardych linków do tego samego programu. Na przykład /bin/ls
, /bin/ps
, /bin/chmod
, /bin/rm
, itd. Mogą być trudne do powiązania /bin/busybox
.
Aby się znaleźć, wykonaj następujące czynności:
Zapisz pwd, PATH i argv [0] przy wejściu do programu (lub inicjalizacji biblioteki), ponieważ mogą się później zmienić.
Opcjonalnie: szczególnie w przypadku systemów innych niż Unix, należy oddzielić, ale nie odrzucać części prefiksu nazwa hosta / użytkownika / dysku, jeśli jest obecny; część, która często poprzedza dwukropek lub występuje po początkowym „//”.
Jeśli argv[0]
jest to ścieżka bezwzględna, użyj jej jako punktu początkowego. Ścieżka bezwzględna prawdopodobnie zaczyna się od „/”, ale w niektórych systemach innych niż Unix może zaczynać się od „\” lub litery dysku lub prefiksu nazwy, po którym następuje dwukropek.
W przeciwnym razie, jeśli argv[0]
jest ścieżką względną (zawiera „/” lub „\”, ale nie zaczyna się od niej, na przykład „../../bin/foo”, a następnie połącz pwd + „/” + argv [0] (użyj obecny katalog roboczy od momentu uruchomienia programu, nie aktualny).
Jeśli argv [0] jest zwykłym basenemame (bez ukośników), połącz go z każdą pozycją w zmiennej środowiskowej PATH i wypróbuj je i użyj pierwszej, która się powiedzie.
Opcjonalnie: Else spróbować samemu od platformy /proc/self/exe
, /proc/curproc/file
(BSD), a (char *)getauxval(AT_EXECFN)
, a dlgetname(...)
jeśli obecny. Możesz nawet wypróbować te wcześniejsze argv[0]
metody, jeśli są one dostępne i nie występują problemy z uprawnieniami. W dość mało prawdopodobnym przypadku (gdy weźmie się pod uwagę wszystkie wersje wszystkich systemów), że są one obecne i nie zawodzą, mogą być bardziej wiarygodne.
Opcjonalnie: sprawdź nazwę ścieżki przekazaną za pomocą parametru wiersza polecenia.
Opcjonalnie: sprawdź, czy ścieżka nie jest w środowisku jawnie przekazana przez skrypt opakowania, jeśli istnieje.
Opcjonalnie: W ostateczności wypróbuj zmienną środowiskową „_”. Może to wskazywać zupełnie inny program, na przykład powłokę użytkownika.
Rozpoznawanie dowiązań symbolicznych, może istnieć wiele warstw. Istnieje możliwość nieskończonych pętli, ale jeśli one istnieją, prawdopodobnie Twój program nie zostanie wywołany.
Kanonizuj nazwę pliku, rozwiązując podciągi, takie jak „/foo/../bar/” do „/ bar /”. Zauważ, że może to potencjalnie zmienić znaczenie, jeśli przekroczysz punkt montowania sieci, więc kanonizacja nie zawsze jest dobrą rzeczą. Na serwerze sieciowym można użyć „..” w dowiązaniu symbolicznym do przejścia do innego pliku w kontekście serwera zamiast na kliencie. W takim przypadku prawdopodobnie potrzebujesz kontekstu klienta, więc kanonizacja jest w porządku. Konwertuj również wzorce, takie jak „/./” na „/” i „//” na „/”. W powłoce readlink --canonicalize
rozwiąże wiele dowiązań symbolicznych i kanonizuje nazwę. Chase może działać podobnie, ale nie jest zainstalowany. realpath()
lub canonicalize_file_name()
, jeśli jest obecny, może pomóc.
Jeśli realpath()
nie istnieje w czasie kompilacji, możesz pożyczyć kopię z licencjonowanej dystrybucji bibliotek i skompilować ją w sobie, zamiast wymyślać koło. Napraw potencjalne przepełnienie bufora (przekaż rozmiar bufora wyjściowego, pomyśl strncpy () vs strcpy ()), jeśli będziesz używał bufora mniejszego niż PATH_MAX. Łatwiej może być po prostu użyć prywatnej kopii o zmienionej nazwie niż testować, czy istnieje. Permissive copy copy from Android / darwin / bsd:
https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c
Należy pamiętać, że wiele prób może zakończyć się powodzeniem lub częściowo i nie wszystkie mogą wskazywać na ten sam plik wykonywalny, dlatego należy rozważyć weryfikację pliku wykonywalnego; jednak możesz nie mieć uprawnienia do odczytu - jeśli nie możesz go odczytać, nie traktuj tego jako niepowodzenia. Lub zweryfikuj coś w pobliżu pliku wykonywalnego, na przykład katalog „../lib/”, który próbujesz znaleźć. Możesz mieć wiele wersji, wersje spakowane i lokalnie skompilowane, wersje lokalne i sieciowe, wersje lokalne i przenośne z napędem USB itp. Istnieje niewielka możliwość, że uzyskasz dwa niezgodne wyniki z różnych metod lokalizacji. „_” Może po prostu wskazywać niewłaściwy program.
Program używający execve
może celowo ustawić argv[0]
niezgodność z rzeczywistą ścieżką użytą do załadowania programu i uszkodzenia PATH, „_”, pwd itp., Chociaż generalnie nie ma zbyt wielu powodów, aby to robić; ale może to mieć wpływ na bezpieczeństwo, jeśli masz wrażliwy kod, który ignoruje fakt, że środowisko wykonawcze można zmienić na różne sposoby, w tym między innymi (chroot, system plików bezpieczników, twarde łącza itp.) Jest to możliwe dla poleceń powłoki, aby ustawić PATH, ale nie można go wyeksportować.
Niekoniecznie musisz kodować w systemach innych niż Unix, ale dobrym pomysłem byłoby zapoznanie się z niektórymi osobliwościami, abyś mógł napisać kod w taki sposób, aby nie było tak trudne dla kogoś późniejszego przeniesienia . Należy pamiętać, że niektóre systemy (DEC VMS, DOS, adresy URL itp.) Mogą mieć nazwy dysków lub inne prefiksy, które kończą się dwukropkiem, np. „C: \”, „sys $ drive: [foo] bar” i „file : /// foo / bar / baz ". Stare systemy DEC VMS używają „[” i „]” do umieszczenia części katalogu w ścieżce, chociaż mogło to ulec zmianie, jeśli Twój program jest skompilowany w środowisku POSIX. Niektóre systemy, takie jak VMS, mogą mieć wersję pliku (oddzieloną średnikiem na końcu). Niektóre systemy używają dwóch następujących po sobie ukośników, takich jak „// dysk / ścieżka / do / pliku” lub „użytkownik @ host: / ścieżka / do / pliku” (polecenie scp) lub „plik: (rozdzielany spacjami) i „PATH” rozdzielany dwukropkami, ale twój program powinien otrzymywać PATH, więc nie musisz się martwić o ścieżkę. DOS i niektóre inne systemy mogą mieć ścieżki względne rozpoczynające się od prefiksu dysku. C: foo.exe odnosi się do foo.exe w bieżącym katalogu na dysku C, więc musisz wyszukać bieżący katalog na C: i użyć go dla pwd. (rozdzielany spacjami) i „PATH” rozdzielany dwukropkami, ale twój program powinien otrzymywać PATH, więc nie musisz się martwić o ścieżkę. DOS i niektóre inne systemy mogą mieć ścieżki względne rozpoczynające się od prefiksu dysku. C: foo.exe odnosi się do foo.exe w bieżącym katalogu na dysku C, więc musisz wyszukać bieżący katalog na C: i użyć go dla pwd.
Przykład dowiązań symbolicznych i opakowań w moim systemie:
/usr/bin/google-chrome is symlink to
/etc/alternatives/google-chrome which is symlink to
/usr/bin/google-chrome-stable which is symlink to
/opt/google/chrome/google-chrome which is a bash script which runs
/opt/google/chome/chrome
Pamiętaj, że rachunek użytkownika opublikował powyższy link do programu w HP, który obsługuje trzy podstawowe przypadki argv[0]
. Potrzebuje jednak pewnych zmian:
- Konieczne będzie przepisanie wszystkich
strcat()
oraz strcpy()
użycie strncat()
i strncpy()
. Mimo że zmienne są deklarowane o długości PATHMAX, wartość wejściowa o długości PATHMAX-1 plus długość połączonych łańcuchów wynosi> PATHMAX, a wartość wejściowa o długości PATHMAX nie byłaby ustalona.
- Należy go przepisać jako funkcję biblioteki, a nie tylko wydrukować wyniki.
- Nie można kanonizować nazw (użyj kodu realpath, do którego odsyłam powyżej)
- Nie można rozpoznać dowiązań symbolicznych (użyj kodu realpath)
Tak więc, jeśli połączysz zarówno kod HP, jak i kod realpath, i naprawisz oba, aby były odporne na przepełnienia bufora, powinieneś mieć coś, co może poprawnie zinterpretować argv[0]
.
Poniżej przedstawiono rzeczywiste wartości argv[0]
różnych sposobów wywoływania tego samego programu w systemie Ubuntu 12.04. I tak, program został przypadkowo nazwany echoargc zamiast echoargv. Dokonano tego przy użyciu skryptu do czystego kopiowania, ale wykonanie go ręcznie w powłoce daje te same wyniki (z wyjątkiem tego, że aliasy nie działają w skrypcie, chyba że wyraźnie je włączysz).
cat ~/src/echoargc.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
main(int argc, char **argv)
{
printf(" argv[0]=\"%s\"\n", argv[0]);
sleep(1); /* in case run from desktop */
}
tcc -o ~/bin/echoargc ~/src/echoargc.c
cd ~
/home/whitis/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
echoargc
argv[0]="echoargc"
bin/echoargc
argv[0]="bin/echoargc"
bin//echoargc
argv[0]="bin//echoargc"
bin/./echoargc
argv[0]="bin/./echoargc"
src/../bin/echoargc
argv[0]="src/../bin/echoargc"
cd ~/bin
*echo*
argv[0]="echoargc"
e?hoargc
argv[0]="echoargc"
./echoargc
argv[0]="./echoargc"
cd ~/src
../bin/echoargc
argv[0]="../bin/echoargc"
cd ~/junk
~/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
argv[0]="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
argv[0]="/home/whitis/bin/echoargc"
ln -s ~/bin/echoargc junk1
./junk1
argv[0]="./junk1"
ln -s /home/whitis/bin/echoargc junk2
./junk2
argv[0]="./junk2"
ln -s junk1 junk3
./junk3
argv[0]="./junk3"
gnome-desktop-item-edit --create-new ~/Desktop
# interactive, create desktop link, then click on it
argv[0]="/home/whitis/bin/echoargc"
# interactive, right click on gnome application menu, pick edit menus
# add menu item for echoargc, then run it from gnome menu
argv[0]="/home/whitis/bin/echoargc"
cat ./testargcscript 2>&1 | sed -e 's/^/ /g'
#!/bin/bash
# echoargc is in ~/bin/echoargc
# bin is in path
shopt -s expand_aliases
set -v
cat ~/src/echoargc.c
tcc -o ~/bin/echoargc ~/src/echoargc.c
cd ~
/home/whitis/bin/echoargc
echoargc
bin/echoargc
bin//echoargc
bin/./echoargc
src/../bin/echoargc
cd ~/bin
*echo*
e?hoargc
./echoargc
cd ~/src
../bin/echoargc
cd ~/junk
~/bin/echoargc
~whitis/bin/echoargc
alias echoit=~/bin/echoargc
echoit
echoarg=~/bin/echoargc
$echoarg
ln -s ~/bin/echoargc junk1
./junk1
ln -s /home/whitis/bin/echoargc junk2
./junk2
ln -s junk1 junk3
./junk3
Te przykłady ilustrują, że techniki opisane w tym poście powinny działać w szerokim zakresie okoliczności i dlaczego niektóre kroki są konieczne.
EDYCJA: Teraz program, który wypisuje argv [0] został zaktualizowany, aby się znalazł.
// Copyright 2015 by Mark Whitis. License=MIT style
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
// "look deep into yourself, Clarice" -- Hanibal Lector
char findyourself_save_pwd[PATH_MAX];
char findyourself_save_argv0[PATH_MAX];
char findyourself_save_path[PATH_MAX];
char findyourself_path_separator='/';
char findyourself_path_separator_as_string[2]="/";
char findyourself_path_list_separator[8]=":"; // could be ":; "
char findyourself_debug=0;
int findyourself_initialized=0;
void findyourself_init(char *argv0)
{
getcwd(findyourself_save_pwd, sizeof(findyourself_save_pwd));
strncpy(findyourself_save_argv0, argv0, sizeof(findyourself_save_argv0));
findyourself_save_argv0[sizeof(findyourself_save_argv0)-1]=0;
strncpy(findyourself_save_path, getenv("PATH"), sizeof(findyourself_save_path));
findyourself_save_path[sizeof(findyourself_save_path)-1]=0;
findyourself_initialized=1;
}
int find_yourself(char *result, size_t size_of_result)
{
char newpath[PATH_MAX+256];
char newpath2[PATH_MAX+256];
assert(findyourself_initialized);
result[0]=0;
if(findyourself_save_argv0[0]==findyourself_path_separator) {
if(findyourself_debug) printf(" absolute path\n");
realpath(findyourself_save_argv0, newpath);
if(findyourself_debug) printf(" newpath=\"%s\"\n", newpath);
if(!access(newpath, F_OK)) {
strncpy(result, newpath, size_of_result);
result[size_of_result-1]=0;
return(0);
} else {
perror("access failed 1");
}
} else if( strchr(findyourself_save_argv0, findyourself_path_separator )) {
if(findyourself_debug) printf(" relative path to pwd\n");
strncpy(newpath2, findyourself_save_pwd, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
realpath(newpath2, newpath);
if(findyourself_debug) printf(" newpath=\"%s\"\n", newpath);
if(!access(newpath, F_OK)) {
strncpy(result, newpath, size_of_result);
result[size_of_result-1]=0;
return(0);
} else {
perror("access failed 2");
}
} else {
if(findyourself_debug) printf(" searching $PATH\n");
char *saveptr;
char *pathitem;
for(pathitem=strtok_r(findyourself_save_path, findyourself_path_list_separator, &saveptr); pathitem; pathitem=strtok_r(NULL, findyourself_path_list_separator, &saveptr) ) {
if(findyourself_debug>=2) printf("pathitem=\"%s\"\n", pathitem);
strncpy(newpath2, pathitem, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
realpath(newpath2, newpath);
if(findyourself_debug) printf(" newpath=\"%s\"\n", newpath);
if(!access(newpath, F_OK)) {
strncpy(result, newpath, size_of_result);
result[size_of_result-1]=0;
return(0);
}
} // end for
perror("access failed 3");
} // end else
// if we get here, we have tried all three methods on argv[0] and still haven't succeeded. Include fallback methods here.
return(1);
}
main(int argc, char **argv)
{
findyourself_init(argv[0]);
char newpath[PATH_MAX];
printf(" argv[0]=\"%s\"\n", argv[0]);
realpath(argv[0], newpath);
if(strcmp(argv[0],newpath)) { printf(" realpath=\"%s\"\n", newpath); }
find_yourself(newpath, sizeof(newpath));
if(1 || strcmp(argv[0],newpath)) { printf(" findyourself=\"%s\"\n", newpath); }
sleep(1); /* in case run from desktop */
}
A oto wynik, który pokazuje, że w każdym z poprzednich testów faktycznie się znalazł.
tcc -o ~/bin/echoargc ~/src/echoargc.c
cd ~
/home/whitis/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
echoargc
argv[0]="echoargc"
realpath="/home/whitis/echoargc"
findyourself="/home/whitis/bin/echoargc"
bin/echoargc
argv[0]="bin/echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
bin//echoargc
argv[0]="bin//echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
bin/./echoargc
argv[0]="bin/./echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
src/../bin/echoargc
argv[0]="src/../bin/echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
cd ~/bin
*echo*
argv[0]="echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
e?hoargc
argv[0]="echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
./echoargc
argv[0]="./echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
cd ~/src
../bin/echoargc
argv[0]="../bin/echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
cd ~/junk
~/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
rm junk1 junk2 junk3
ln -s ~/bin/echoargc junk1
./junk1
argv[0]="./junk1"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
ln -s /home/whitis/bin/echoargc junk2
./junk2
argv[0]="./junk2"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
ln -s junk1 junk3
./junk3
argv[0]="./junk3"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
Dwa wyżej opisane uruchomienia GUI również poprawnie znajdują program.
Istnieje jedna potencjalna pułapka. access()
Funkcja spada uprawnienia, jeżeli program jest setuid przed badaniem. Jeśli istnieje sytuacja, w której program może zostać znaleziony jako użytkownik z podwyższonym poziomem uprawnień, ale nie jako zwykły użytkownik, wówczas może wystąpić sytuacja, w której testy te zakończą się niepowodzeniem, chociaż jest mało prawdopodobne, aby program mógł zostać uruchomiony w takich okolicznościach. Zamiast tego można użyć euidaccess (). Możliwe jest jednak, że program może znaleźć niedostępny program na ścieżce wcześniej niż rzeczywisty użytkownik.