mprotect(2)

الاسم
mprotect, pkey_mprotect - ضبط الحماية على منطقة من الذاكرة

المكتبة
مكتبة سي المعيارية (libc، -lc)

موجز
#include <sys/mman.h>

int mprotect(size_t size;
             void addr[size], size_t size, int prot);

#define _GNU_SOURCE             /* انظر feature_test_macros(7) */
#include <sys/mman.h>

int pkey_mprotect(size_t size;
             void addr[size], size_t size, int prot, int pkey);

الوصف
تغير mprotect() حماية الوصول لصفحات ذاكرة العملية المستدعية التي تحتوي على أي جزء من نطاق العناوين في الفترة [addr, addr+size-1]. يجب أن يكون addr محاذياً لحدود الصفحة.

إذا حاولت العملية المستدعية الوصول إلى الذاكرة بطريقة تنتهك الحماية، فإن النواة تولد إشارة SIGSEGV للعملية.

prot هو مزيج من أعلام الوصول التالية: PROT_NONE أو عملية OR بتية للقيم الأخرى في القائمة التالية:

PROT_NONE
    لا يمكن الوصول إلى الذاكرة على الإطلاق.

PROT_READ
    يمكن قراءة الذاكرة.

PROT_WRITE
    يمكن تعديل الذاكرة.

PROT_EXEC
    يمكن تنفيذ الذاكرة.

PROT_SEM (منذ Linux 2.5.7)
    يمكن استخدام الذاكرة للعمليات الذرية. تم تقديم هذا العلم كجزء من تنفيذ futex(2) (لضمان القدرة على أداء العمليات الذرية المطلوبة بأوامر مثل FUTEX_WAIT)، لكنه لا يُستخدم حالياً على أي بنية.

PROT_SAO (منذ Linux 2.6.26)
    يجب أن يكون للذاكرة ترتيب وصول قوي. هذه الميزة خاصة ببنية PowerPC (الإصدار 2.06 من مواصفات البنية يضيف ميزة SAO CPU، وهي متاحة على POWER 7 أو PowerPC A2، على سبيل المثال).

بالإضافة (منذ Linux 2.6.0)، يمكن أن يحتوي prot على أحد الأعلام التالية:

PROT_GROWSUP
    طبق نمط الحماية حتى نهاية تعيين ينمو لأعلى. (يتم إنشاء هذه التعيينات لمنطقة المكدس على البنى—على سبيل المثال، HP-PARISC—التي تحتوي على مكدس ينمو لأعلى.)

PROT_GROWSDOWN
    طبق نمط الحماية نزولاً إلى بداية تعيين ينمو لأسفل (يجب أن يكون مقطع مكدس أو مقطع معين مع تعيين علم MAP_GROWSDOWN).

مثل mprotect()، تغير pkey_mprotect() الحماية على الصفحات المحددة بواسطة addr و size. تحدد الوسيطة pkey مفتاح الحماية (انظر pkeys(7)) لتعيينه للذاكرة. يجب تخصيص مفتاح الحماية باستخدام pkey_alloc(2) قبل تمريره إلى pkey_mprotect(). لمثال على استخدام هذه الاستدعاءات النظامية، انظر pkeys(7).

قيمة الإرجاع
عند النجاح، ترجع mprotect() و pkey_mprotect() صفراً. عند الخطأ، ترجع هذه الاستدعاءات النظامية -1، ويتم تعيين errno للإشارة إلى الخطأ.

الأخطاء

EACCES
    لا يمكن إعطاء الذاكرة الوصول المحدد. يمكن أن يحدث هذا، على سبيل المثال، إذا قمت بـ mmap(2) لملف لديك وصول للقراءة فقط إليه، ثم طلبت من mprotect() وضع علامة PROT_WRITE عليه.

EINVAL
    addr ليس مؤشراً صالحاً، أو ليس مضاعفاً لحجم صفحة النظام.

EINVAL
    (pkey_mprotect()) لم يتم تخصيص pkey باستخدام pkey_alloc(2)

EINVAL
    تم تحديد كل من PROT_GROWSUP و PROT_GROWSDOWN في prot.

EINVAL
    أعلام غير صالحة محددة في prot.

EINVAL
    (بنية PowerPC) تم تحديد PROT_SAO في prot، لكن ميزة الأجهزة SAO غير متاحة.

ENOMEM
    لا يمكن تخصيص هياكل النواة الداخلية.

ENOMEM
    العناوين في النطاق [addr, addr+size-1] غير صالحة لمساحة عنوان العملية، أو تحدد صفحة واحدة أو أكثر غير معينة. (قبل Linux 2.4.19، كان الخطأ EFAULT يُنتج بشكل غير صحيح لهذه الحالات.)

ENOMEM
    تغيير حماية منطقة ذاكرة سيؤدي إلى تجاوز العدد الإجمالي للتعيينات ذات السمات المميزة (مثل الحماية للقراءة مقابل الحماية للقراءة/الكتابة) الحد الأقصى المسموح به. (على سبيل المثال، جعل حماية نطاق PROT_READ في منتصف منطقة محمية حاليًا كـ PROT_READ|PROT_WRITE سيؤدي إلى ثلاثة تعيينات: تعيينان للقراءة/الكتابة في كل طرف وتعيين للقراءة فقط في المنتصف.)

الإصدارات
ينص POSIX على أن سلوك mprotect() غير محدد إذا تم تطبيقه على منطقة ذاكرة لم يتم الحصول عليها عبر mmap(2).

في لينكس، من الجائز دائمًا استدعاء mprotect() على أي عنوان في مساحة عنوان العملية (باستثناء منطقة vsyscall للنواة). على وجه الخصوص، يمكن استخدامه لتغيير تعيينات الكود الحالية لتكون قابلة للكتابة.

ما إذا كان PROT_EXEC له أي تأثير مختلف عن PROT_READ يعتمد على بنية المعالج، وإصدار النواة، وحالة العملية. إذا تم تعيين READ_IMPLIES_EXEC في أعلام شخصية العملية (انظر personality(2))، فإن تحديد PROT_READ سيضيف ضمنيًا PROT_EXEC.

في بعض بنيات العتاد (مثل i386)، يستلزم PROT_WRITE PROT_READ.

ينص POSIX.1 على أن التنفيذ قد يسمح بالوصول بخلاف ما هو محدد في prot، ولكن كحد أدنى يمكنه السماح بالوصول للكتابة فقط إذا تم تعيين PROT_WRITE، ويجب ألا يسمح بأي وصول إذا تم تعيين PROT_NONE.

يجب على التطبيقات توخي الحذر عند خلط استخدام mprotect() و pkey_mprotect(). في x86، عند استخدام mprotect() مع تعيين prot إلى PROT_EXEC، قد يتم تخصيص مفتاح حماية (pkey) وتعيينه على الذاكرة ضمنيًا بواسطة النواة، ولكن فقط عندما كان المفتاح 0 سابقًا.

في الأنظمة التي لا تدعم مفاتيح الحماية في العتاد، لا يزال من الممكن استخدام pkey_mprotect() ولكن يجب تعيين pkey إلى -1. عند الاستدعاء بهذه الطريقة، تكون عملية pkey_mprotect() مكافئة لـ mprotect().

المعايير

mprotect()
    POSIX.1-2024.

pkey_mprotect()
    لينكس.

التاريخ

mprotect()
    SVr4, SUSv1, POSIX.1-1996.

pkey_mprotect()
    لينكس 4.9، glibc 2.27.

ملاحظات

أمثلة
البرنامج أدناه يوضح استخدام mprotect(). يخصص البرنامج أربع صفحات من الذاكرة، ويجعل الصفحة الثالثة منها للقراءة فقط، ثم ينفذ حلقة تمر تصاعديًا عبر المنطقة المخصصة لتعديل البايتات.

مثال لما قد نراه عند تشغيل البرنامج هو التالي:

    $ ./a.out
    Start of region:        0x804c000
    Got SIGSEGV at address: 0x804e000

  مصدر البرنامج
    #include <err.h>
    #include <malloc.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    #include <unistd.h>

    static char *buffer;

    static void
    handler(int sig, siginfo_t *si, void *unused)
    {
        /* Note: calling printf() from a signal handler is not safe
           (and should not be done in production programs), since
           printf() is not async-signal-safe; see signal-safety(7).
           Nevertheless, we use printf() here as a simple way of
           showing that the handler was called.  */

        printf("Got SIGSEGV at address: %p\n", si->si_addr);
        exit(EXIT_FAILURE);
    }

    int
    main(void)
    {
        int               pagesize;
        struct sigaction  sa;

        sa.sa_flags = SA_SIGINFO;
        sigemptyset(&sa.sa_mask);
        sa.sa_sigaction = handler;
        if (sigaction(SIGSEGV, &sa, NULL) == -1)
            err(EXIT_FAILURE, "sigaction");

        pagesize = sysconf(_SC_PAGE_SIZE);
        if (pagesize == -1)
            err(EXIT_FAILURE, "sysconf");

        /* Allocate a buffer aligned on a page boundary;
           initial protection is PROT_READ | PROT_WRITE.  */

        buffer = memalign(pagesize, 4 * pagesize);
        if (buffer == NULL)
            err(EXIT_FAILURE, "memalign");

        printf("Start of region:        %p\n", buffer);

        if (mprotect(buffer + pagesize * 2, pagesize,
                     PROT_READ) == -1)
            err(EXIT_FAILURE, "mprotect");

        for (char *p = buffer ; ; )
            *(p++) = 'a';

        printf("Loop completed\n");     /* Should never happen */
        exit(EXIT_SUCCESS);
    }

انظر أيضًا
mmap(2), sysconf(3), pkeys(7)

ترجمة
تُرجمت هذه الصفحة من الدليل بواسطة زايد السعيدي <zayed.alsaidi@gmail.com>

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 (https://www.gnu.org/licenses/gpl-3.0.html) أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

إذا وجدت أي أخطاء في ترجمة صفحة الدليل هذه، يرجى إرسال بريد إلكتروني إلى قائمة بريد المترجمين: kde-l10n-ar@kde.org.