मेरे पास निम्न कोड है: फ़ंक्शन get_unlimited_input एक नई स्ट्रिंग आवंटित करता है यदि NULL पारित किया गया था, अन्यथा यह केवल मौजूदा स्ट्रिंग में वर्णों को जोड़ता है। अंत में यह अतिरिक्त बाइट्स को काट देता है। (DEFAULT_BUFFER_SIZE को 5 पर सेट किया गया था ताकि कई वास्तविक आवंटन के मामले का परीक्षण किया जा सके)

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

#define DEFAULT_BUFFER_SIZE 5

char *get_unlimited_input(char *buffer) {
    size_t current_size;
    if (buffer == NULL) {
        buffer = malloc(DEFAULT_BUFFER_SIZE * sizeof(char));
        current_size = DEFAULT_BUFFER_SIZE;
    } else {
        current_size = strlen(buffer) + DEFAULT_BUFFER_SIZE;
    }
    char *cursor = buffer + current_size - DEFAULT_BUFFER_SIZE;
    for (;;) {
        int current = getchar();
        *cursor = (char)current;
        cursor++;
        if (current == '\n' || current == EOF)
            break;
        if (cursor >= buffer + current_size) {
            current_size += DEFAULT_BUFFER_SIZE;
            buffer = realloc(buffer, current_size);
            cursor = buffer + current_size - DEFAULT_BUFFER_SIZE;
        }
    }
    *cursor = '\0';
    buffer = realloc(buffer, cursor - buffer);
    return buffer;
}

int main() {
    printf(">");
    char *buffer = get_unlimited_input(NULL);
    printf(">");
    get_unlimited_input(buffer);
}

ज्यादातर मामलों में यह ठीक काम करता है, लेकिन अगर मैं पहले 117 अक्षर पास करता हूं, और फिर 12 यह दुर्घटनाग्रस्त हो जाता है:

>.....................................................................................................................
>............
realloc(): invalid next size
Aborted (core dumped)
python3 -c "print('.'*117+'\n'+'.'*12)" | ./_buffer
realloc(): invalid next size
Aborted (core dumped)

समस्या क्या है?

2
Fedora 25 जिंदा 2021, 22:32

4 जवाब

सबसे बढ़िया उत्तर

आपके कोड में कई समस्याएं हैं, जिससे डायग्नोस्टिक इंगित करने पर हीप दूषित हो जाता है:

  • वर्तमान में आवंटित आकार के बारे में आपकी धारणा गलत है: current_size = strlen(buffer) + DEFAULT_BUFFER_SIZE; बहुत आशावादी है। चूंकि आप बफ़र को वापस करने से पहले cursor - buffer बाइट्स में पुन: आवंटित करते हैं, स्ट्रिंग के अंत में कोई सुस्ती नहीं है।

  • आप बाइट को सरणी में संग्रहीत करने के बाद '\n' और EOF के लिए परीक्षण करते हैं। यह न्यूलाइन के लिए इच्छित व्यवहार हो सकता है, लेकिन यह EOF के लिए गलत है, जो एक वर्ण नहीं है।

  • buffer को buffer = realloc(buffer, cursor - buffer); के साथ पुन: आवंटित करना भी गलत है: cursor शून्य टर्मिनेटर को इंगित करता है, इसलिए आपको आवंटित ब्लॉक के अंदर शून्य टर्मिनेटर रखने के लिए cursor + 1 - buffer के आकार का उपयोग करना चाहिए।

यहाँ एक संशोधित संस्करण है:

#include <stdio.h>
#include <stdlib.h>

#define DEFAULT_BUFFER_SIZE  16  /* use address alignment as incremental size */

char *get_unlimited_input(char *buffer) {
    size_t current_size, pos;
    char *p;

    if (buffer == NULL) {
        pos = 0;
        current_size = DEFAULT_BUFFER_SIZE;
        buffer = malloc(DEFAULT_BUFFER_SIZE);
        if (buffer == NULL)
            return NULL;
    } else {
        pos = strlen(buffer);
        current_size = pos + 1;
    }
    for (;;) {
        int c = getchar();
        if (c == EOF || c == '\0')
            break;
        if (pos + 1 == current_size) {
            // reallocate the buffer
            current_size += DEFAULT_BUFFER_SIZE;
            p = realloc(buffer, current_size);
            if (p == NULL)
                break;
            buffer = p;
        }
        buffer[pos++] = (char)c;
        if (c == '\n')
            break;
    }
    buffer[pos] = '\0';
    p = realloc(buffer, pos + 1);
    return (p != NULL) ? p : buffer;
}

int main() {
    printf("> ");
    char *buffer = get_unlimited_input(NULL);
    printf("got: %s\n", buffer);
    printf("> ");
    get_unlimited_input(buffer);
    printf("got: %s\n", buffer);
    return 0;
}
1
Wai Ha Lee 26 जिंदा 2021, 00:16

अगर आपको malloc, realloc या free से रनटाइम त्रुटि मिलती है, तो इसका मतलब है कि आपने हीप को दूषित कर दिया है। ढेर भ्रष्टाचार के सामान्य कारणों में स्मृति ब्लॉक को मुक्त होने के बाद उपयोग करना शामिल है (इसमें free दो बार कॉल करना शामिल है) और बफर ओवरफ्लो (और अंडरफ्लो)।

भ्रष्टाचार रनटाइम त्रुटि से पहले हुआ। यह बहुत पहले हो सकता है, इसलिए यदि आप त्रुटि होने पर प्रोग्राम को डीबग करना शुरू करते हैं, तो जो हुआ उसे फिर से बनाना मुश्किल हो सकता है। ऐसे अन्य उपकरण हैं जो समस्या का अधिक सटीक रूप से पता लगाने में आपकी सहायता कर सकते हैं। GCC या Clang वाले यूनिक्स जैसे सिस्टम पर, AddressSanitizer बहुत उपयोगी है।

# Settings for Ubuntu 20.04; you may need to adapt for your system
$ export ASAN_OPTIONS=symbolize=1 ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-10/bin/llvm-symbolizer
$ gcc -O -Wall -Wextra a.c -fsanitize=address,undefined && python3 -c "print('.'*117+'\n'+'.'*12)" | ./a.out
=================================================================
==446177==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60c000000236 at pc 0x7f246fc0ea6d bp 0x7ffd4e309380 sp 0x7ffd4e308b28
READ of size 119 at 0x60c000000236 thread T0
    #0 0x7f246fc0ea6c  (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x67a6c)
    #1 0x55c04dfb32e7 in get_unlimited_input (.../65891246/a.out+0x12e7)
    #2 0x55c04dfb34b1 in main (.../65891246/a.out+0x14b1)
    #3 0x7f246f06f0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
    #4 0x55c04dfb320d in _start (.../65891246/a.out+0x120d)

0x60c000000236 is located 0 bytes to the right of 118-byte region [0x60c0000001c0,0x60c000000236)
allocated by thread T0 here:
    #0 0x7f246fcb4ffe in __interceptor_realloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10dffe)
    #1 0x55c04dfb345c in get_unlimited_input (.../65891246/a.out+0x145c)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x67a6c) 
Shadow bytes around the buggy address:
  0x0c187fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c187fff8000: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c187fff8010: fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa fa
  0x0c187fff8020: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa
  0x0c187fff8030: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
=>0x0c187fff8040: 00 00 00 00 00 00[06]fa fa fa fa fa fa fa fa fa
  0x0c187fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c187fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c187fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c187fff8080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c187fff8090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==446177==ABORTING

आप। सबसे महत्वपूर्ण भाग स्टैक ट्रेस हैं, और यह संकेत है कि "118-बाइट क्षेत्र" पर बफर ओवरफ़्लो हुआ, जो बताता है कि यह get_unlimited_input के पहले आह्वान के अंत में या बहुत शुरुआत में हो रहा है। दूसरा एक। स्टैक ट्रेस आपको सटीक कोड पता देता है जिस पर ओवरफ़्लो होता है, जिसका उपयोग आप डीबगर में ब्रेकपॉइंट सेट करने के लिए कर सकते हैं; आप देखेंगे कि यह फ़ंक्शन के अंत के करीब है। जैसा कि अन्य पहले ही नोट कर चुके हैं,

    *cursor = '\0';
    buffer = realloc(buffer, cursor - buffer);

गलत है: आप '\0' टर्मिनेटर के लिए जगह नहीं छोड़ रहे हैं। आप की जरूरत है

    *(cursor++) = '\0';
    buffer = realloc(buffer, cursor - buffer);

या

    *cursor = '\0';
    buffer = realloc(buffer, cursor - buffer + 1);

मैंने अन्य बग्स की समीक्षा नहीं की है (यह केवल एक ही नहीं है)।

1
Gilles 'SO- stop being evil' 25 जिंदा 2021, 22:51

मूल समस्या यह है कि जब आप get_unlimited_input को नॉन-नल पॉइंटर (पहले की कॉल से पहले से मौजूद बफर) के साथ कॉल करते हैं, तो यह मानता है कि बफर का आकार strlen(buffer) + DEFAULT_BUFFER_SIZE है, जो गलत है। पिछली कॉल वास्तव में स्ट्रिंग की लंबाई से मेल खाने के लिए बफर को फिर से आवंटित कर देगी, जिसमें समाप्ति एनयूएल शामिल नहीं है (जिसका अर्थ है कि एनयूएल को समाप्त करना स्वयं ही खो सकता है।)

आप एनयूएल को स्टोर करने के बाद और फिर से आवंटित करने से पहले कर्सर बढ़ाकर वृद्धि करके इन्हें ठीक कर सकते हैं (इसलिए रीयलोक काफी बड़ा होगा), और फिर एक गैर-शून्य सूचक को strlen(buffer) + 1 होने पर current_size की गणना को बदलना

एक और समस्या यह है कि जब आप गेटचर से ईओएफ प्राप्त करते हैं, तो आप उस ईओएफ को एक चार में डाल रहे हैं और इसे बफर में संग्रहीत कर रहे हैं। एक EOF एक मान्य वर्ण नहीं है - getchar होने का संपूर्ण बिंदु char के बजाय एक int लौटाता है ताकि यह EOF को किसी भी वर्ण से अलग संकेत दे सके। इस प्रकार ईओएफ पर आप बफर में कुछ यादृच्छिक कचरा चरित्र (या गैर-चरित्र) संग्रहित कर रहे हैं, जो केवल कचरे के रूप में दिखाई दे सकता है, या आउटपुट पर क्रैश या त्रुटि का कारण बन सकता है (सिस्टम के आधार पर)।

2
Chris Dodd 25 जिंदा 2021, 22:42

अन्य समस्याओं के अलावा, आप इसे वापस करने से पहले बफर से सभी अतिरिक्त स्थान को ट्रिम कर देते हैं। लेकिन अगर आप फ़ंक्शन में बफर में जाते हैं, तो आप मानते हैं कि इसमें अभी भी अतिरिक्त जगह है। इस प्रकार आपको फ़ंक्शन से वापस फ़ंक्शन पर लौटाए गए बफर को पास नहीं करना चाहिए। लेकिन आप ठीक यही करते हैं।

    } else {
        current_size = strlen(buffer) + DEFAULT_BUFFER_SIZE;
    }
...
    buffer = realloc(buffer, cursor - buffer);

साथ ही, जैसा कि कामिलकुक द्वारा बताया गया है, आप लौटाए गए स्ट्रिंग में टर्मिनेटर के लिए जगह नहीं छोड़ते हैं, इसलिए strlen पर कॉल करना सुरक्षित नहीं है।

आपको यह दस्तावेज करना चाहिए कि फ़ंक्शन के इनपुट पर क्या आवश्यकताएं हैं और फ़ंक्शन के आउटपुट को संतुष्ट करने के लिए किन आवश्यकताओं की गारंटी है। इससे इस तरह की गलतियों का पता लगाना बहुत आसान हो जाता है।

जैसे ही आप देखते हैं, "यदि इस फ़ंक्शन में एक बफर पास किया गया है, तो इसमें अतिरिक्त स्थान होना चाहिए" और "इस फ़ंक्शन से लौटाए गए बफर में कोई अतिरिक्त स्थान नहीं है", यह स्पष्ट है कि आप लौटाए गए बफर को वापस पास नहीं कर सकते हैं कार्य करता है क्योंकि आउटपुट गारंटी इनपुट आवश्यकताओं को पूरा नहीं करती है।

3
David Schwartz 25 जिंदा 2021, 22:39