2011-01-01 7 views
13

तो, मुझे पता है कि लिनक्स x86 प्रोसेसर (कर्नेल कोड, कर्नेल डेटा, उपयोगकर्ता कोड, उपयोगकर्ता डेटा) के लिए चार डिफ़ॉल्ट खंडों का उपयोग करता है, लेकिन उनके पास एक ही आधार और सीमा (0x00000000 और 0xfffff) है, जिसका अर्थ है प्रत्येक सेगमेंट मैप्स रैखिक पते का एक ही सेट।x86 पर लिनक्स उपयोगकर्ता प्रक्रियाओं और कर्नेल के लिए अलग सेगमेंट का उपयोग क्यों करता है?

यह देखते हुए, उपयोगकर्ता/कर्नेल सेगमेंट क्यों हैं? मैं समझता हूं कि कोड और डेटा के लिए अलग-अलग सेगमेंट क्यों होना चाहिए (केवल x86 प्रोसेसर सीएस और डीएस रजिस्ट्रार से कैसे संबंधित है), लेकिन क्यों एक कोड सेगमेंट और एक डेटा सेगमेंट नहीं है? मेमोरी सुरक्षा पेजिंग के माध्यम से की जाती है, और उपयोगकर्ता और कर्नेल सेगमेंट वैसे भी उसी रैखिक पते पर नक्शा करता है।

उत्तर

11

86 आर्किटेक्चर सहयोगियों एक प्रकार है और प्रत्येक खंड विवरणक के साथ एक विशेषाधिकार का स्तर:

से एड का संदर्भ लें। एक वर्णनकर्ता का प्रकार केवल खंडों को पढ़ने, पढ़ने/लिखने, निष्पादन योग्य इत्यादि को पढ़ने की अनुमति देता है, लेकिन समान आधार और सीमा वाले विभिन्न सेगमेंट के लिए मुख्य कारण एक अलग वर्णक विशेषाधिकार स्तर (डीपीएल) का उपयोग करने की अनुमति देना है।

डीपीएल दो बिट्स है, जो 0 से 3 मानों को एन्कोड करने की इजाजत देता है। जब विशेषाधिकार स्तर 0 होता है, तो इसे ring 0 कहा जाता है, जो सबसे अधिक विशेषाधिकार प्राप्त है। लिनक्स कर्नेल के लिए सेगमेंट डिस्क्रिप्टर रिंग 0 हैं जबकि उपयोगकर्ता स्पेस के लिए सेगमेंट डिस्क्रिप्टर रिंग 3 (कम से कम विशेषाधिकार प्राप्त) हैं। यह अधिकांश खंडित ऑपरेटिंग सिस्टम के लिए सच है; ऑपरेटिंग सिस्टम के मुख्य अंगूठी 0 और बाकी अंगूठी 3. है

लिनक्स कर्नेल, सेट के रूप में आप उल्लेख किया है, चार खंडों:

  • __KERNEL_CS (कर्नेल कोड खंड आधार = 0, सीमा = 4 जीबी, type = 10, डीपीएल = 0)
  • __KERNEL_DS (कर्नेल डेटा खंड, आधार = 0, सीमा = 4GB, type = 2, डीपीएल = 0)
  • __USER_CS (उपयोगकर्ता कोड खंड, आधार = 0, सीमा = 4 जीबी, टाइप = 10, डीपीएल = 3)
  • __USER_DS (उपयोगकर्ता डेटा सेगमेंट, बेस = 0, सीमा = 4 जीबी, टाइप = 2, डीपीएल = 3)

सभी चार का आधार और सीमा समान है, लेकिन कर्नेल सेगमेंट डीपीएल 0 हैं, उपयोगकर्ता खंड डीपीएल 3 हैं, कोड सेगमेंट निष्पादन योग्य और पठनीय (लिखने योग्य नहीं हैं), और डेटा सेगमेंट पठनीय हैं और लिखने योग्य (निष्पादन योग्य नहीं)।

यह भी देखें:

+0

ठीक है, तो डीपीएल प्रत्येक खंड के लिए न्यूनतम सुरक्षा स्तर सेट, लेकिन ऐसा लगता है जैसे कि मैं किसी भी रैखिक पते को उपयोगकर्ता के रूप में एक्सेस कर सकता हूं, तो कर्नेल के लिए अतिरिक्त सेगमेंट क्यों है? यदि, उपयोगकर्ता के रूप में, मैं मेमोरी एड्रेस एक्स एक्सेस करना चाहता हूं, तो मैं एक्स के ऑफसेट के साथ उपयोगकर्ता डेटा सेगमेंट का उपयोग करता हूं। कर्नेल कर्नेल डेटा सेगमेंट का एक्स के ऑफसेट के साथ उपयोग कर सकता है, लेकिन यह एक ही रैखिक पते पर नक्शा कर सकता है, इस प्रकार भौतिक स्मृति में एक ही पता है, तो यह कोई सुरक्षा कैसे प्रदान करता है? –

+1

@anjruu: कुछ असेंबली निर्देशों को एक निश्चित विशेषाधिकार स्तर की आवश्यकता होती है या फिर एक सामान्य सुरक्षा (जीपी) गलती उठाई जाती है। उदाहरण के लिए, बंदरगाह से बाइट पढ़ने के लिए 'IN' निर्देश के लिए वर्तमान पीएल (सीपीएल) इनपुट/आउटपुट पीएल (आईओपीएल; बिट्स 12 और 13' फ्लैग्स रजिस्टर के बराबर या उससे कम होने की आवश्यकता होती है, जो कि लिनक्स के लिए 0 है। सीपीएल 'सीएस' (कोड सेगमेंट) रजिस्टर से संबंधित सेगमेंट डिस्क्रिप्टर का डीपीएल है। –

+0

@ Daniel: Gotcha, यह समझ में आता है। धन्यवाद! –

-1

कर्नेल मेमोरी उपयोगकर्ता स्थान में चल रहे प्रोग्रामों से पठनीय नहीं होनी चाहिए।

प्रोग्राम डेटा अक्सर निष्पादन योग्य नहीं होता है (डीईपी, एक प्रोसेसर सुविधा, जो एक अतिप्रवाह बफर और अन्य दुर्भावनापूर्ण हमलों को निष्पादित करने में सुरक्षा में मदद करता है)।

यह सब कुछ नियंत्रण नियंत्रण के बारे में है - विभिन्न खंडों के अलग-अलग अधिकार हैं। यही कारण है कि गलत सेगमेंट तक पहुंचने से आपको "सेगमेंटेशन गलती" मिल जाएगी।

2

x86 मेमोरी प्रबंधन आर्किटेक्चर दोनों सेगमेंटेशन और पेजिंग का उपयोग करता है। बहुत मोटे तौर पर बोलते हुए, एक सेगमेंट एक प्रक्रिया के पता स्थान का विभाजन है जिसमें इसकी अपनी सुरक्षा नीति है। तो, x86 आर्किटेक्चर में, मेमोरी पतों की श्रेणी को विभाजित करना संभव है कि एक प्रक्रिया कई संगत खंडों में देखती है, और प्रत्येक को अलग-अलग सुरक्षा मोड असाइन करती है। पेजिंग वास्तविक, भौतिक स्मृति के टुकड़ों के लिए प्रक्रिया की पता स्थान के छोटे (आमतौर पर 4 केबी) क्षेत्रों को मैप करने के लिए एक तकनीक है। इस प्रकार पेजिंग इस बात को नियंत्रित करता है कि सेगमेंट के अंदर के क्षेत्र भौतिक RAM पर मैप किए जाते हैं।

  1. एक खंड (पते 0x00000000 0xBFFFFFFF के माध्यम से) उपयोगकर्ता के स्तर, प्रक्रिया-विशिष्ट डेटा के लिए इस तरह के कार्यक्रम के कोड, स्थिर डेटा, ढेर, और ढेर के रूप में:

    सभी प्रक्रियाओं को दो खंडों की है। प्रत्येक प्रक्रिया का अपना, स्वतंत्र उपयोगकर्ता खंड होता है।

  2. एक सेगमेंट (0xC0000000 0xFFFFFFFF के माध्यम से संबोधित करता है), जिसमें कर्नेल निर्देश, डेटा, कुछ स्टैक जैसे कर्नेल कोड निष्पादित हो सकते हैं, और अधिक दिलचस्प बात यह है कि इस सेगमेंट में एक क्षेत्र सीधे मैप किया गया है भौतिक स्मृति, ताकि कर्नेल सीधे पता अनुवाद के बारे में चिंता किए बिना भौतिक स्मृति स्थानों तक पहुंच सके। एक ही कर्नेल सेगमेंट को प्रत्येक प्रक्रिया में मैप किया जाता है, लेकिन संरक्षित कर्नेल मोड में निष्पादित करते समय प्रक्रिया केवल इसे एक्सेस कर सकती है।

तो, उपयोगकर्ता मोड में, प्रक्रिया केवल 0xC0000000 से कम पते तक पहुंच सकती है; किसी गलती से इस परिणाम से अधिक पते पर कोई पहुंच। हालांकि, जब उपयोगकर्ता-मोड प्रक्रिया कर्नेल में निष्पादित करना शुरू करती है (उदाहरण के लिए, सिस्टम कॉल करने के बाद), CPU में सुरक्षा बिट पर्यवेक्षक मोड में बदल जाता है (और कुछ सेगमेंटेशन रजिस्ट्रार बदल जाते हैं), जिसका अर्थ है कि प्रक्रिया है इस प्रकार 0xC0000000 से ऊपर के पते तक पहुंचने में सक्षम है। HERE

0

X86 में - लिनक्स खंड रजिस्टरों बफर अतिप्रवाह जांच करने के लिए उपयोग किया जाता है [नीचे कोड स्निपेट देखना जिसने stac में कुछ चार सरणी परिभाषित की है के]:

static void 
printint(int xx, int base, int sgn) 
{ 
    char digits[] = "ABCDEF"; 
    char buf[16]; 
    int i, neg; 
    uint x; 

    neg = 0; 
    if(sgn && xx < 0){ 
     neg = 1; 
     x = -xx; 
    } else { 
     x = xx; 
    } 

    i = 0; 
    do{ 
     buf[i++] = digits[x % base]; 
    }while((x /= base) != 0); 
    if(neg) 
     buf[i++] = '-'; 

    while(--i >= 0) 
     my_putc(buf[i]); 
} 

अब यदि हम कोड जीसीसी-जेनरेट कोड के असेंबली को देखते हैं। समारोह printint के लिए कोडांतरक कोड के

डंप:

0x00000000004005a6 <+0>: push %rbp 
    0x00000000004005a7 <+1>: mov %rsp,%rbp 
    0x00000000004005aa <+4>: sub $0x50,%rsp 
    0x00000000004005ae <+8>: mov %edi,-0x44(%rbp) 


    0x00000000004005b1 <+11>: mov %esi,-0x48(%rbp) 
    0x00000000004005b4 <+14>: mov %edx,-0x4c(%rbp) 
    0x00000000004005b7 <+17>: mov %fs:0x28,%rax ------> obtaining an 8 byte guard from based on a fixed offset from fs segment register [from the descriptor base in the corresponding gdt entry] 
    0x00000000004005c0 <+26>: mov %rax,-0x8(%rbp) -----> pushing it as the first local variable on to stack 
    0x00000000004005c4 <+30>: xor %eax,%eax 
    0x00000000004005c6 <+32>: movl $0x33323130,-0x20(%rbp) 
    0x00000000004005cd <+39>: movl $0x37363534,-0x1c(%rbp) 
    0x00000000004005d4 <+46>: movl $0x42413938,-0x18(%rbp) 
    0x00000000004005db <+53>: movl $0x46454443,-0x14(%rbp) 

... 
... 
    // function end 

    0x0000000000400686 <+224>: jns 0x40066a <printint+196> 
    0x0000000000400688 <+226>: mov -0x8(%rbp),%rax -------> verifying if the stack was smashed 
    0x000000000040068c <+230>: xor %fs:0x28,%rax --> checking the value on stack is matching the original one based on fs 
    0x0000000000400695 <+239>: je  0x40069c <printint+246> 
    0x0000000000400697 <+241>: callq 0x400460 <[email protected]> 
    0x000000000040069c <+246>: leaveq 
    0x000000000040069d <+247>: retq 

अब अगर हम इस समारोह से ढेर आधारित चार सरणियों निकालने के लिए, जीसीसी यह गार्ड की जांच नहीं होगी।

मैंने कर्नेल मॉड्यूल के लिए भी जीसीसी द्वारा उत्पन्न किया है। असल में मैं कुछ कर्नेल कोड को बोट्रैप करते समय एक क्रैश देख रहा था और यह वर्चुअल एड्रेस 0x28 के साथ गलती कर रहा था। बाद में मैंने सोचा कि मैंने सोचा था कि मैंने स्टैक पॉइंटर को सही तरीके से शुरू किया है और प्रोग्राम को सही तरीके से लोड किया है, मेरे पास जीडीटी में सही प्रविष्टियां नहीं हैं, जो एफएस आधारित ऑफ़सेट को वैध आभासी पते में अनुवादित करेगी।

हालांकि कर्नेल कोड के मामले में यह केवल अनदेखा कर रहा था, __stack_chk_fail @ plt> जैसे कुछ पर कूदने की बजाय त्रुटि।

प्रासंगिक कंपाइलर विकल्प जो इस गार्ड को जीसीसी में जोड़ता है -फस्टैक-रक्षक है। मुझे लगता है कि यह डिफ़ॉल्ट रूप से सक्षम है जो उपयोगकर्ता ऐप को संकलित करता है।

कर्नेल के लिए, हम कॉन्फ़िगरेशन CC_STACKPROTECTOR विकल्प के माध्यम से इस जीसीसी ध्वज को सक्षम कर सकते हैं।

 
config CC_STACKPROTECTOR 
699  bool "Enable -fstack-protector buffer overflow detection (EXPERIMENTAL)" 
700  depends on SUPERH32 
701  help 
702   This option turns on the -fstack-protector GCC feature. This 
703   feature puts, at the beginning of functions, a canary value on 
704   the stack just before the return address, and validates 
705   the value just before actually returning. Stack based buffer 
706   overflows (that need to overwrite this return address) now also 
707   overwrite the canary, which gets detected and the attack is then 
708   neutralized via a kernel panic. 
709 
710   This feature requires gcc version 4.2 or above. 

प्रासंगिक गिरी फ़ाइल जहां इस जी एस/FS है linux/चाप/86/शामिल/ASM/stackprotector.h

संबंधित मुद्दे