لماذا تحصل على وظيفة خطيرة للغاية لا ينبغي استخدامها؟

StackOverflow https://stackoverflow.com/questions/1694036

  •  18-09-2019
  •  | 
  •  

سؤال

عندما أحاول ترجمة رمز C الذي يستخدم gets() وظيفة مع دول مجلس التعاون الخليجي، أحصل على هذا التحذير:

(.Text + 0x34): تحذير: وظيفة "يحصل" خطيرة ولا ينبغي استخدامها.

أتذكر أن هذا لديه علاقة بحماية المكدس والأمن، لكنني لست متأكدا بالضبط.

كيف يمكنني إزالة هذا التحذير ولماذا هناك مثل هذا التحذير حول استخدام gets()?

إذا gets() هو خطير جدا ثم لماذا لا يمكننا إزالته؟

هل كانت مفيدة؟

المحلول

لكي تستخدم gets بأمان، عليك أن تعرف بالضبط عدد الأحرف التي ستقرأها، بحيث يمكنك جعل المخزن المؤقت الخاص بك كبير بما فيه الكفاية. سوف تعرف فقط أنه إذا كنت تعرف بالضبط ما هي البيانات التي ستقرأها.

بدلا من استخدام gets, ، تريد استخدامها fgets, ، الذي لديه التوقيع

char* fgets(char *string, int length, FILE * stream);

(fgets, ، إذا كان يقرأ خط كامل، فسيغادر '\n' في السلسلة سيكون عليك التعامل مع ذلك.)

ظل جزءا رسميا من اللغة حتى معيار ISO C لعام 1999، لكن تم إزالته رسميا بحلول عام 2011. لا تزال معظم تطبيقات ج لا تزال تدعمها، ولكن لا تصدر دول مجلس التعاون الخليجي على الأقل تحذيرا لأي رمز يستخدمه.

نصائح أخرى

لماذا gets() خطير

أول دودة الإنترنت ( موريس دودة الإنترنت) نجا منذ حوالي 30 عاما (1988-11-02)، وتستخدم gets() وفائدة مؤقتة باعتبارها واحدة من أساليبها للنشر من النظام إلى النظام. المشكلة الأساسية هي أن الوظيفة لا تعرف مدى سرعة المخزن المؤقت هو، لذلك يستمر في القراءة حتى يجد خط جديد أو مواجهات EOF، وقد تفيض حدود المخزن المؤقت الذي قدمته.

يجب أن تنسى أنك سمعت ذلك gets() موجودة.

C11 القياسية ISO / IEC 9899: 2011 القضاء gets() كدالة قياسية، ما هو شيء جيد ™ (تم وضع علامة رسميا على أنه "قديم" و "مهملت" في ISO / IEC 9899: 1999 / COR.3: 2007 - التصويب الفني 3 ل C99، ثم تمت إزالته في C11) وبعد للأسف، ستبقى في مكتبات لسنوات عديدة (يعني "العقود") لأسباب التوافق الخلفي. إذا كان الأمر متروك لي، تنفيذ gets() قد يصبح:

char *gets(char *buffer)
{
    assert(buffer != 0);
    abort();
    return 0;
}

بالنظر إلى أن الكود الخاص بك سوف يتعطل على أي حال، عاجلا أم آجلا، من الأفضل أن تتجه مشكلة في إيقاف عاجلا وليس أحدث. سأكون مستعدا لإضافة رسالة خطأ:

fputs("obsolete and dangerous function gets() called\n", stderr);

الإصدارات الحديثة من نظام تجميع Linux يولد تحذيرات إذا كنت تصل gets() - وكذلك لبعض المهام الأخرى التي لديها أيضا مشاكل أمنية (mktemp(), …).

بدائل gets()

fgets ()

كما قال الجميع، البديل الكنسي ل gets() يكون fgets() تحديد stdin كدفق الملف.

char buffer[BUFSIZ];

while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
    ...process line of data...
}

ما لا أحد آخر ذكر ذلك هو ذلك gets() لا يشمل الخط الجديد ولكن fgets() هل. لذلك، قد تحتاج إلى استخدام التفاف حولها fgets() أن يحذف الخط الجديد:

char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
    if (fgets(buffer, buflen, fp) != 0)
    {
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        return buffer;
    }
    return 0;
}

أو، أفضل:

char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
    if (fgets(buffer, buflen, fp) != 0)
    {
        buffer[strcspn(buffer, "\n")] = '\0';
        return buffer;
    }
    return 0;
}

أيضا، وكذلك كوف يشير في تعليق و paxdiablo. يظهر في إجابته، مع fgets() قد يكون لديك بيانات متبقية على خط. يترك رمز التفاف الخاص بي أن البيانات التي ستكون في المرة القادمة؛ يمكنك بسهولة تعديلها لتخصيص بقية خط البيانات إذا كنت تفضل:

        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        else
        {
             int ch;
             while ((ch = getc(fp)) != EOF && ch != '\n')
                 ;
        }

المشكلة المتبقية هي كيفية الإبلاغ عن الدول الثلاث المختلفة للدول - EOF أو الخطأ، وقراءة السطر وغير مقطوع، وقراءة الخط الجزئي ولكن تم اقتطاع البيانات.

هذه المشكلة لا تنشأ gets() لأنه لا يعرف أين ينتهي المخزن المؤقت الخاص بك ومتعاون بمرض ما وراء النهاية، فإنه يسيء فسادا على تخطيط الذاكرة الذي تمام بشكل جميل، وغالبا ما يعبث مكدس العودة (أ فائض المكدس. لا شيء من هذه فكرة جيدة - يقومون بتصحيح عبارة "السلوك غير معروف".


هناك أيضا TR 24731-1. (التقرير الفني من اللجنة القياسية C) التي توفر بدائل أكثر أمانا لمجموعة متنوعة من الوظائف، بما في ذلك gets():

§ 6.5.4.1 gets_s وظيفة

ملخص

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

قيود وقت التشغيل

s يجب ألا يكون مؤشرا فارغا. n لا يساوي صفر ولا يكون أكبر من rsize_max. يجب أن يحدث حرف جديد أو نهاية الملف أو خطأ أثناء القراءة n-1 شخصيات من stdin.25)

3 إذا كان هناك انتهاك لقيد وقت التشغيل، s[0] تم ضبطه على الحرف الفارغ، ويتم قراءة الأحرف والتخلص منها stdin حتى تتم قراءة حرف جديد، يحدث خطأ في نهاية الملف أو خطأ للقراءة.

وصف

4 gets_s تعمل الوظيفة على الأكثر قليلا من عدد الأحرف المحددة بواسطة nمن الدفق المشار إليه stdin, في الصفيف أشار إليها s. وبعد لا تتم قراءة أحرف إضافية بعد حرف جديد (تم تجاهله) أو بعد نهاية الملف. لا تعتمد حرف الخط الجديد المهمل نحو قراءة عدد الأحرف. تتم كتابة حرف فارغ مباشرة بعد قراءة الشخصية الأخيرة في الصفيف.

5 إذا تمت مصادفة نهاية الملف ولم يتم قراءة أي شخصيات في الصفيف، أو في حالة حدوث خطأ في العملية أثناء العملية s[0] تم ضبطه على الحرف الفارغ، والعناصر الأخرى ل s تأخذ القيم غير المحددة.

الممارسات الموصى بها

6 و fgets تتيح الدالة برامج مكتوبة بشكل صحيح على معالجة خطوط المدخلات بأمان لفترة طويلة جدا لتخزينها في صفيف النتائج. بشكل عام وهذا يتطلب من المتصلين fgets انتبه إلى وجود أو عدم وجود شخصية خط جديد في صفيف النتائج. فكر في استخدام fgets (جنبا إلى جنب مع أي معالجة حاجة بناء على أحرف الخط الجديد) بدلا من gets_s.

25) ال gets_s وظيفة، على عكس gets, ، يجعلها انتهاكا للقيود في وقت التشغيل لخط الإدخال لزيادة المخزن المؤقت لتخزينه. على عكس fgets, gets_s يحافظ على علاقة واحدة بين خطوط المدخلات والمكالمات الناجحة gets_s. وبعد البرامج التي تستخدم gets نتوقع مثل هذه العلاقة.

يقوم برنامج التحويل البرمجيات من Microsoft Visual Studio بتنفيذ تقريب معيار TR 24731-1، ولكن هناك اختلافات بين التوقيعات التي تنفذها Microsoft وتلك الموجودة في TR.

يتضمن معيار C11، ISO / IEC 9899-2011، TR24731 في الملحق ك كجزء اختياري من المكتبة. لسوء الحظ، نادرا ما يتم تنفيذها على أنظمة مثل UNIX.


getline() - بوسيكس

يوفر بوسيكس 2008 أيضا بديلا آمنا ل gets() مسمى getline(). وبعد يخصص مساحة للخط ديناميكيا، لذلك ينتهي بك الأمر إلى تحريرها. يزيل القيد على طول الخط، وبالتالي. كما أنه بإرجاع طول البيانات التي كانت تقرأ، أو -1 (و لا EOF!)، مما يعني أن البايتات الخالية في المدخلات يمكن التعامل معها بشكل موثوق. هناك أيضا "اختر الاختلاف الخاص بك محدد حرف واحد الخاص بك getdelim(); ؛ هذا يمكن أن يكون مفيدا إذا كنت تتعامل مع الإخراج من find -print0 حيث يتم وضع علامة نهايات أسماء الملفات مع NUL ASCII '\0' شخصية، على سبيل المثال.

لأن gets لا تفعل أي نوع من الشيك أثناء الحصول على بايت من ستوتن ووضعها في مكان ما. مثال بسيط:

char array1[] = "12345";
char array2[] = "67890";

gets(array1);

الآن، أولا وقبل كل شيء يسمح لك بإدخال عدد الأحرف التي تريدها، gets لن يهتم بذلك. ثانيا البايت فوق حجم الصفيف الذي تضع فيه (في هذه الحالة array1هل الكتابة فوق كل ما وجدوه في الذاكرة بسبب gets سوف يكتب لهم. في المثال السابق، هذا يعني أنه إذا دخلت "abcdefghijklmnopqrts" ربما، لا يمكن التنبؤ بها، وسوف يبتلأ أيضا array2 أو أيا كان.

الوظيفة غير آمنة لأنها تفترض إدخال متسقة. أبدا استخدامه!

يجب أن لا تستخدم gets لأنه لا يوجد لديه طريقة لوقف الفضاد المخزن المؤقت. إذا كانت مستخدمي المستخدمين في مزيد من البيانات أكثر مما يمكن أن يصلح في المخزن المؤقت الخاص بك، فمن المحتمل أن ينتهي بك الأمر بالفساد أو الأسوأ.

في الواقع، اتخذت ISO في الواقع خطوة إزالة gets من المعيار C (اعتبارا من C11، على الرغم من أنه تم إهماله في C99)، إلا أنه بالنظر إلى مدى زيادة التوافق المتخلص، يجب أن يكون مؤشرا على مدى سوء الوظيفة.

الشيء الصحيح الذي يجب القيام به هو استخدام fgets وظيفة مع stdin مقبض الملفات لأنه يمكنك الحد من الأحرف يقرأ من المستخدم.

ولكن هذا لديه أيضا مشاكله مثل:

  • سيتم التقاط أحرف إضافية دخلت المستخدم في المرة القادمة.
  • لا يوجد إعلام سريع أن المستخدم دخل الكثير من البيانات.

تحقيقا لهذه الغاية، ستكتب كل مبرمز C تقريبا في مرحلة ما في حياتهم المهنية مجمعا أكثر فائدة حولها fgets كذلك. هنا لي:

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

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

مع بعض رمز الاختبار:

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        printf ("No input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long\n");
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}

يوفر نفس الحماية كما fgets في ذلك، يمنع تجاوزات المخزن المؤقت، لكنه يخطر أيضا بالمتصل فيما يتعلق بما حدث وائماء الأحرف الزائدة حتى لا يؤثر على تشغيل الإدخال التالي.

لا تتردد في استخدامه كما يحلو لك إطلاق سراحه بموجب "افعل ما تريد جيدا" :-)

fets..

لقراءة من ستودين:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */

لا يمكنك إزالة وظائف API دون كسر API. إذا كنت ترغب في تجميع العديد من التطبيقات أو تشغيلها على الإطلاق.

هذا هو السبب في ذلك مرجع واحد يعطي

قراءة خط يفيض الصفيف يشير إلى النتائج في السلوك غير المحدد. يوصى باستخدام FGETS ().

قرأت مؤخرا، في Usenet البريد إلى comp.lang.c, ، الذي - التي gets() يتم إزالته من المعيار. Woohoo.

ستكون سعيدا بمعرفة أن اللجنة صوتت للتو (بالإجماع، حيث اتضح) لإزالة GETS () من المشروع كذلك.

في C11 (ISO / IEC 9899: 201X)، gets() تم إزالته. (تم إهمالها في ISO / IEC 9899: 1999 / COR. 2007 (ه))

بالإضافة إلى fgets(), ، C11 يقدم بديلا آمنا جديدا gets_s():

C11 K.3.5.4.1 gets_s وظيفة

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

ومع ذلك، في الممارسات الموصى بها الجزء، fgets() لا يزال المفضل.

ال fgets تتيح الدالة برامج مكتوبة بشكل صحيح على معالجة خطوط المدخلات بأمان لفترة طويلة جدا لتخزينها في صفيف النتائج. بشكل عام وهذا يتطلب من المتصلين fgets انتبه إلى وجود أو عدم وجود شخصية خط جديد في صفيف النتائج. فكر في استخدام fgets (جنبا إلى جنب مع أي معالجة حاجة بناء على أحرف الخط الجديد) بدلا من gets_s.

أرغب في توسيع دعوة جادة لأي مواضيع مكتبة ج هناك ما زالوا يشملون gets في مكتباتهم "فقط في حالة عدم وجود أي شخص اعتمادا عليه": يرجى استبدال تطبيقك مع ما يعادل

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}

هذا سيساعد على التأكد من أن لا أحد لا يزال اعتمادا عليه. شكرا لك.

gets() أمر خطير لأنه من الممكن أن يحطم المستخدم البرنامج عن طريق كتابة الكثير في المطالبة. لا يمكن اكتشاف نهاية الذاكرة المتوفرة، لذلك إذا قمت بتخصيص كمية من الذاكرة صغيرة جدا لهذا الغرض، فيمكن أن يسبب خطأ SEG وتعطل. في بعض الأحيان يبدو من غير المرجح أن يكتب المستخدم 1000 حرفا في موجه مخصص لاسم الشخص، ولكن كمبرمجين، نحتاج إلى جعل برامجنا مقاومة للرصاص. (قد يكون أيضا مخاطرة أمنية إذا كان يمكن للمستخدم أن يحطم برنامجا للنظام عن طريق إرسال الكثير من البيانات).

fgets() يتيح لك تحديد عدد الأحرف التي يتم بها إخراجها من المخزن المؤقت الإدخال القياسي، لذلك لا يتم تجاوز المتغير.

G يحصل وظيفة خطيرة وكان خطأ مكلف للغاية. Tony Hoare Bodyles من أجل ذكر محدد في حديثه "إشارات فارغة: خطأ مليار دولار":

http://www.infoq.com/presentations/null-references-the-billion-dollar-mistake-tony-hoare.

الساعة بأكملها تستحق المشاهدة، ولكن من أجل عرض تعليقاته من 30 دقيقة مع الحصول على انتقادات محددة حوالي 39 دقيقة.

نأمل أن هذا يثير شهيتك للحديث بأكمله، مما يسلم الانتباه إلى كيف نحتاج إلى مزيد من البراهين صحة أكثر رسمية بلغات، وكيف ينبغي إلقاء اللوم على المصممين باللغة في الأخطاء في لغاتهم، وليس المبرمج. يبدو أن هذا هو السبب الكلامل المشكوك فيه لمصممي اللغات السيئة لدفع اللوم إلى المبرمجين في ستار "حرية المبرمج".

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top