2017-10-23 2 views
1

Linux에서 ARG_MAX를 올바르게 계산하는 방법에 대한 몇 페이지 (예 : this one)를 읽었습니다. 내가 알 수있는 한, 각 인수/환경 변수는 포인터의 크기를 취하고, 하나는 null로 끝나는 문자열 자체의 길이를 더한 값입니다. 패딩으로 추가 공간을 차지할 가능성이 있습니다. 그러나 2K 여분의 헤드 룸을 제공 한 후에도 exec() 긴 명령 행을 시도 할 때 E2BIG를 실행 중입니다. 이 문제의 원인은 무엇입니까?인수와 환경을 고려할 때 exec()에서 E2BIG를 가져 오는 이유는 무엇입니까?

나는 그것이 auxiliary vector일지도 모른다고 생각했지만 크기 (320 바이트)를 추가한다고해도 도움이되지 않습니다. 나는 또한 MAX_ARG_STRINGSMAX_ARG_STRLEN보다 훨씬 작습니다 (https://unix.stackexchange.com/a/120842/56202 참조).

#include <assert.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 

extern char **environ; 

size_t arg_size(const char *arg) { 
     // Count the string as well as the argv/environ pointer to it 
     size_t size = sizeof(arg); 
     if (arg) { 
       size += strlen(arg) + 1; 
     } 
     return size; 
} 

int main() { 
     size_t arg_max = sysconf(_SC_ARG_MAX); 
     arg_max -= 2048; // POSIX recommends 2048 bytes of additional headroom 
     printf("arg_max: %zu\n", arg_max); 

     size_t size = 320; // For the auxiliary vector 

     for (char **envp = environ; *envp; ++envp) { 
       size += arg_size(*envp); 
     } 
     size += arg_size(NULL); 

     char *argv[100001] = {"true"}; 
     size += arg_size(argv[0]); 
     size += arg_size(NULL); 

     char *arg = "ABCDEFABCDEF" 
        "ABCDEFABCDEF"; 
     size_t each_size = arg_size(arg); 
     size_t i; 
     for (i = 1; i < 100000 && size + each_size < arg_max; ++i) { 
       argv[i] = arg; 
       size += each_size; 
     } 
     argv[i] = NULL; 

     printf("size: %zu, argc: %zu\n", size, i); 
     assert(size < arg_max); 

     execvp(argv[0], argv); 
     perror("execvp()"); 
     return EXIT_FAILURE; 
} 
완성도를 들어
$ gcc e2big.c -o e2big && ./e2big 
arg_max: 2095104 
size: 2095059, argc: 28640 
execvp(): Argument list too long 

,이 UB 세계에

$ uname -a 
Linux superluminal 4.13.7-1-ARCH #1 SMP PREEMPT Sat Oct 14 20:13:26 CEST 2017 x86_64 GNU/Linux 
+0

포인터의 크기를 계산하는 것은 말이되지 않습니다. –

+0

@ Jean-FrançoisFabre 물론 'argv' 배열 자체가 가리키는 데이터와 마찬가지로 공간을 차지합니다. –

+0

그는 sizeof (arg)가 가리키는 데이터의 크기가 아니라 포인터의 크기라는 것을 의미한다고 생각합니다. –

답변

0

알아 냈어! 첫째, argv[]envp[], E2BIG에 대해 계산하는 방식으로 어떤 이유로 실행에 또한 exec() 시스템 호출 구현이 사본의 전체 경로를,뿐만 아니라 :

retval = copy_strings_kernel(1, &bprm->filename, bprm); 
if (retval < 0) 
     goto out; 

bprm->exec = bprm->p; 
retval = copy_strings(bprm->envc, envp, bprm); 
if (retval < 0) 
     goto out; 

retval = copy_strings(bprm->argc, argv, bprm); 
if (retval < 0) 
     goto out; 

https://github.com/torvalds/linux/blob/v4.13/fs/exec.c#L1775

둘째, 인수 문자열은 한 번에 한 페이지 씩 복사되며 전체 페이지의 세분성을 기준으로 한 제한과 비교됩니다.

https://github.com/torvalds/linux/blob/v4.13/fs/exec.c#L222

unsigned long size = bprm->vma->vm_end - bprm->vma->vm_start; 
unsigned long ptr_size, limit; 
... 
ptr_size = (bprm->argc + bprm->envc) * sizeof(void *); 
if (ptr_size > ULONG_MAX - size) 
     goto fail; 
size += ptr_size; 
그래서 단지 내가 포인터의 크기를 추가하기 전에 페이지 크기의 배수에 해당을 모아 가지고, 내가 인수/환경 문자열의 총 길이를 계산해야하지. 또는 간단히하기 위해 최소한 헤드 룸의 전체 페이지를 제공해야합니다.

-1
char *argv[100000] = {"true"}; 
    .... 
    for (i = 1; i < 100000 && size + each_size < arg_max; ++i) { 
      argv[i] = arg; 
      size += each_size; 
    } 
    argv[i] = NULL; 

에 오신 것을 환영에 있습니다. 루프가 종료되면 i은 100000이며 argv[100000]을 터치하면 안됩니다.

순수 추측 다음 argv 배열 스택에 할당되고, 단지 컴파일러가 일회성 요소가 printf에 의해 절단하는 방법을 알고있다.

+0

틀림없이 실수는 있지만, 반복은 28640에서 반복된다. –

+0

확실합니다. 그 NULL이 겹쳐 쓰면 ('argv [100000]'이 "빌린 메모리"이기 때문에), argv는 더 이상 생각하지 않는 곳에서 종료되지 않습니다. – ikegami

+0

여기서 무엇을 의미하는지 모르겠습니다 ...이 코드를 실행할 때'size + each_size