의 사용은 /proc/self/exe
비 휴대용 및 신뢰할 수 없다. Ubuntu 12.04 시스템에서 심볼릭 링크를 읽고 따르려면 루트 사용자 여야합니다. 이렇게하면 Boost 예제가 작성되고 whereami()
게시 된 솔루션이 실패 할 수 있습니다.
이 게시물은 매우 길지만 실제 문제에 대해 설명하고 실제로 테스트 스위트에 대한 유효성 검사와 함께 작동하는 코드를 제공합니다.
프로그램을 찾는 가장 좋은 방법은 시스템이 사용하는 것과 동일한 단계를 다시 추적하는 것입니다. 이는 argv[0]
파일 시스템 루트, pwd, 경로 환경에 대해 해결 을 사용 하고 심볼릭 링크 및 경로 이름 정규화를 고려 하여 수행됩니다 . 이것은 메모리에서 왔지만 과거에는이 작업을 성공적으로 수행했으며 다양한 상황에서 테스트했습니다. 작동한다고 보장되지는 않지만 문제가 더 크지 않을 경우 논의 된 다른 방법보다 전체적으로 더 안정적입니다. Unix 호환 시스템에는 적절한 처리 방법이 있습니다argv[0]
프로그램을 하지 않지만 인증 된 환경에서 실행 중입니다. 1970 년 이래로 모든 유닉스 파생 시스템과 상당히 유닉스 파생 시스템에도 이식성이 뛰어나다. 기본적으로 libc () 표준 기능과 표준 명령 줄 기능에 의존하기 때문이다. Linux (모든 버전), Android, Chrome OS, Minix, 원본 Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, 다음 단계 등 약간만 수정하면 VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2 등이 될 수 있습니다. GUI 환경에서 직접 프로그램을 시작한 argv[0]
경우 절대 경로로 설정해야 합니다.
기본적으로 출시 된 모든 유닉스 호환 운영 체제의 거의 모든 셸은 기본적으로 동일한 방식으로 프로그램을 찾고 운영 환경을 거의 동일한 방식으로 설정합니다 (일부 옵션 추가 옵션 포함). 그리고 프로그램을 시작하는 다른 프로그램은 쉘에서 실행되는 것과 같은 환경 (argv, 환경 문자열 등)을 추가 옵션과 함께 생성 할 것으로 예상됩니다. 프로그램이나 사용자는 시작되는 다른 하위 프로그램에 대해이 규칙에서 벗어난 환경을 설정할 수 있지만, 그렇지 않은 경우 이는 버그이며 하위 프로그램이나 해당 하위 프로그램이 올바르게 작동 할 것으로 기대할 수 없습니다.
가능한 값은 argv[0]
다음 과 같습니다.
/path/to/executable
— 절대 경로
../bin/executable
— pwd와 관련
bin/executable
— pwd와 관련
./foo
— pwd와 관련
executable
— 기본 이름, 경로에서 찾기
bin//executable
— 비정규적인 암호
src/../bin/executable
— 비밀번호, 비정규, 역 추적
bin/./echoargc
— 비정규적인 암호
볼 수없는 값 :
~/bin/executable
— 프로그램이 실행되기 전에 다시 작성되었습니다.
~user/bin/executable
— 프로그램이 실행되기 전에 다시 작성
alias
— 프로그램이 실행되기 전에 다시 작성
$shellvariable
— 프로그램이 실행되기 전에 다시 작성
*foo*
— 와일드 카드, 프로그램이 실행되기 전에 다시 작성되었지만 그다지 유용하지 않음
?foo?
— 와일드 카드, 프로그램이 실행되기 전에 다시 작성되었지만 그다지 유용하지 않음
또한 비정규 경로 이름과 여러 계층의 심볼릭 링크가 포함될 수 있습니다. 경우에 따라 동일한 프로그램에 대한 여러 개의 하드 링크가있을 수 있습니다. 예를 들어, /bin/ls
, /bin/ps
, /bin/chmod
, /bin/rm
, 등으로 하드 링크를 할 수있다 /bin/busybox
.
자신을 찾으려면 아래 단계를 따르십시오.
나중에 변경 될 수 있으므로 pwd, PATH 및 argv [0]을 프로그램 시작 (또는 라이브러리 초기화)에 저장하십시오.
선택 사항 : 특히 비 유닉스 시스템의 경우 경로 이름 호스트 / 사용자 / 드라이브 접두어 부분이 있으면 분리하지만 버리지 마십시오. 콜론 앞에 오는 부분이나 초기 "//"뒤에 오는 부분.
경우 argv[0]
절대 경로이며, 출발 지점 것을 사용한다. 절대 경로는 아마도 "/"로 시작하지만 유닉스가 아닌 일부 시스템에서는 "\"로 시작하거나 드라이브 문자 나 이름 접두사 뒤에 콜론이 올 수 있습니다.
그렇지 않으면 argv[0]
상대 경로 인 경우 ( "/"또는 "\"를 포함하지만 "../../bin/foo"와 같이 시작하지 않는 경우 pwd + "/"+ argv [0]을 사용하십시오 (사용 현재가 아닌 프로그램 시작 시점의 현재 작업 디렉토리).
그렇지 않으면 argv [0]이 일반 기본 이름 (슬래시 없음) 인 경우 PATH 환경 변수의 각 항목과 차례로 조합하여 시도하고 성공한 첫 번째 항목을 사용하십시오.
옵션 : 그렇지 않으면 아주 플랫폼 특정 시도 /proc/self/exe
, /proc/curproc/file
(BSD)를하고 (char *)getauxval(AT_EXECFN)
,하고 dlgetname(...)
있는 경우. argv[0]
사용 가능하고 권한 문제가 발생하지 않는 경우 이전 기반 방법을 사용해 볼 수도 있습니다. 거의 존재하지 않는 경우 (모든 시스템의 모든 버전을 고려할 때) 시스템이 존재하고 실패하지 않는 경우에는 더 신뢰할 수 있습니다.
선택 사항 : 명령 행 매개 변수를 사용하여 전달 된 경로 이름을 확인하십시오.
선택 사항 : 랩퍼 스크립트가 명시 적으로 전달한 환경의 경로 이름이 있는지 확인하십시오 (있는 경우).
선택 사항 : 최후의 수단으로 환경 변수 "_"를 시도하십시오. 사용자 쉘과 같은 다른 프로그램을 가리킬 수도 있습니다.
심볼릭 링크를 해결하면 여러 계층이있을 수 있습니다. 무한 루프의 가능성이 있지만, 존재한다면 프로그램이 호출되지 않을 것입니다.
"/foo/../bar/"와 같은 하위 문자열을 "/ bar /"로 해석하여 파일 이름을 정규화하십시오. 네트워크 탑재 지점을 통과하면 의미가 변경 될 수 있으므로 정식화가 항상 좋은 것은 아닙니다. 네트워크 서버에서 symlink의 ".."를 사용하여 클라이언트 대신 서버 컨텍스트에서 다른 파일의 경로를 탐색 할 수 있습니다. 이 경우 클라이언트 컨텍스트를 원할 수 있으므로 정규화가 가능합니다. 또한 "/./"와 같은 패턴을 "/"로, "//"를 "/"로 변환하십시오. 쉘에서 readlink --canonicalize
여러 심볼릭 링크를 해결하고 이름을 정식화합니다. 체이스는 비슷하지만 설치되지는 않습니다. realpath()
또는있는 canonicalize_file_name()
경우 도움이 될 수 있습니다.
realpath()
컴파일 타임에 존재하지 않는 경우 , 라이센스가 허가 된 라이브러리 배포본에서 사본을 빌려서 바퀴를 다시 만들지 않고 직접 컴파일 할 수 있습니다. PATH_MAX보다 작은 버퍼를 사용하려면 잠재적 버퍼 오버 플로우 (출력 버퍼의 크기를 전달하고 strncpy ()와 strcpy ()를 생각하십시오)를 수정하십시오. 이름이 바뀐 개인 사본이 있으면 테스트하기보다는 이름을 바꾸는 것이 더 쉬울 수 있습니다. android / darwin / bsd의 허용 라이센스 사본 :
https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c
여러 번의 시도가 성공하거나 부분적으로 성공할 수 있으며 모두 같은 실행 파일을 가리 키지는 않을 수도 있으므로 실행 파일을 확인하십시오. 그러나 읽기 권한이 없을 수도 있습니다. 읽을 수없는 경우이를 실패로 취급하지 마십시오. 또는 찾으려고하는 "../lib/"디렉토리와 같이 실행 파일과 근접한 것을 확인하십시오. 여러 버전, 패키지 및 로컬로 컴파일 된 버전, 로컬 및 네트워크 버전, 로컬 및 USB 드라이브 휴대용 버전 등이있을 수 있으며 다른 위치 지정 방법으로 인해 두 가지 호환되지 않는 결과가 발생할 수 있습니다. "_"는 단순히 잘못된 프로그램을 가리킬 수 있습니다.
를 사용하는 프로그램 은 프로그램을로드하는 데 사용 된 실제 경로와 호환되지 execve
않도록 설정 argv[0]
하고 PATH, "_", pwd 등을 손상시킬 수는 있지만 일반적으로 그렇게 할 이유는 많지 않습니다. 그러나 실행 환경이이를 포함하여 다양한 방식으로 변경 될 수 있다는 사실을 무시하는 취약한 코드가있는 경우 보안에 영향을 줄 수 있습니다 (chroot, 퓨즈 파일 시스템, 하드 링크 등). 쉘 명령이 PATH를 설정하지만 내 보내지 못합니다.
유닉스 이외의 시스템에서는 반드시 코딩 할 필요는 없지만 일부 특성을 알고 있으면 나중에 다른 사람이 이식하기 어려운 방식으로 코드를 작성할 수 있습니다. . 일부 시스템 (DEC VMS, DOS, URL 등)에는 "C : \", "sys $ drive : [foo] bar"및 "file과 같이 콜론으로 끝나는 드라이브 이름 또는 기타 접두사가있을 수 있습니다. : /// foo / bar / baz " 이전 DEC VMS 시스템은 "["및 "]"를 사용하여 프로그램이 POSIX 환경에서 컴파일 된 경우 경로의 디렉토리 부분을 묶습니다. VMS와 같은 일부 시스템에는 파일 버전이있을 수 있습니다 (끝 부분에 세미콜론으로 구분됨). 일부 시스템은 "// drive / path / to / file"또는 "user @ host : / path / to / file"(scp 명령) 또는 "file과 같이 두 개의 연속 슬래시를 사용합니다. (공백으로 구분) 및 "PATH"는 콜론으로 구분되지만 프로그램은 PATH를 수신해야하므로 경로에 대해 걱정할 필요가 없습니다. DOS 및 일부 다른 시스템에는 드라이브 접두어로 시작하는 상대 경로가있을 수 있습니다. C : foo.exe는 C 드라이브의 현재 디렉토리에서 foo.exe를 참조하므로 C :에서 현재 디렉토리를 찾아 pwd에 사용해야합니다. (공백으로 구분) 및 "PATH"는 콜론으로 구분되지만 프로그램은 PATH를 수신해야하므로 경로에 대해 걱정할 필요가 없습니다. DOS 및 일부 다른 시스템에는 드라이브 접두어로 시작하는 상대 경로가있을 수 있습니다. C : foo.exe는 C 드라이브의 현재 디렉토리에서 foo.exe를 참조하므로 C :에서 현재 디렉토리를 찾아 pwd에 사용해야합니다.
내 시스템의 심볼릭 링크 및 래퍼 예 :
/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
사용자 청구서 게시 위의 세 가지 기본 사례를 처리하는 HP 프로그램에 대한 링크를 했습니다 argv[0]
. 그래도 약간의 변경이 필요합니다.
- 그것은 다시 작성해야 할 것입니다 모든
strcat()
과strcpy()
사용에 strncat()
와 strncpy()
. 변수가 길이 PATHMAX로 선언되었지만 길이 PATHMAX-1의 입력 값과 연결된 문자열의 길이는> PATHMAX이고 길이 PATHMAX의 입력 값은 종료되지 않습니다.
- 결과를 인쇄하기보다는 라이브러리 함수로 다시 작성해야합니다.
- 이름을 정식화하지 못합니다 (위의 링크 코드를 사용하십시오)
- 심볼릭 링크를 확인하지 못합니다 (실시간 코드 사용).
따라서 HP 코드와 realpath 코드를 모두 결합하고 버퍼 오버플로에 견딜 수 있도록 수정하면 올바르게 해석 할 수있는 것이 있어야합니다 argv[0]
.
다음 argv[0]
은 Ubuntu 12.04에서 동일한 프로그램을 호출하는 다양한 방법 에 대한 실제 값을 보여줍니다 . 그리고 네, 프로그램 이름은 실수로 echoargv 대신 echoargc로 명명되었습니다. 이것은 깨끗한 복사를 위해 스크립트를 사용하여 수행되었지만 쉘에서 수동으로 수행하면 동일한 결과를 얻습니다 (명명하게 사용하도록 설정하지 않으면 스크립트에서 별칭이 작동하지 않음).
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
이 예는이 게시물에서 설명하는 기술이 다양한 상황에서 작동해야하며 일부 단계가 필요한 이유를 보여줍니다.
편집 : 이제 argv [0]을 인쇄하는 프로그램이 실제로 스스로 찾기 위해 업데이트되었습니다.
// 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 */
}
그리고 여기 이전의 모든 테스트에서 실제로 발견되었다는 것을 보여주는 결과가 있습니다.
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"
위에서 설명한 두 가지 GUI 실행도 프로그램을 올바르게 찾습니다.
하나의 잠재적 함정이 있습니다. 이 access()
기능은 테스트 전에 프로그램이 setuid 인 경우 권한을 삭제합니다. 일반 사용자가 아닌 관리자 권한으로 프로그램을 찾을 수있는 상황이있는 경우에는 이러한 테스트가 실패 할 수 있지만 이러한 상황에서 프로그램을 실제로 실행할 수는 없습니다. 대신 euidaccess ()를 사용할 수 있습니다. 그러나 실제 사용자보다 경로에서 더 일찍 액세스 할 수없는 프로그램을 찾을 수 있습니다.