19

つまり、Linuxはx86プロセッサに4つのデフォルトセグメント(カーネルコード、カーネルデータ、ユーザーコード、ユーザーデータ)を使用しますが、それらはすべて同じベースと制限(0x00000000と0xfffff)を持ち、各セグメントが同じにマップされることを意味します線形アドレスのセット。

これを考えると、なぜユーザー/カーネルセグメントさえあるのでしょうか?コードとデータに別々のセグメントが必要な理由は理解していますが(x86プロセッサがcsレジスタとdsレジスタを処理する方法のため)、単一のコードセグメントと単一のデータセグメントがないのはなぜですか?メモリ保護はページングによって行われ、ユーザーセグメントとカーネルセグメントはとにかく同じ線形アドレスにマップされます。

4

4 に答える 4

15

x86アーキテクチャは、タイプと特権レベルを各セグメント記述子に関連付けます。記述子のタイプにより、セグメントを読み取り専用、読み取り/書き込み、実行可能などにすることができますが、同じベースと制限を持つ異なるセグメントの主な理由は、異なる記述子特権レベル(DPL)を使用できるようにするためです。

DPLは2ビットであり、0から3の値をエンコードできます。特権レベルが0の場合、最も特権の高いリング0と呼ばれます。Linuxカーネルのセグメント記述子はリング0ですが、ユーザースペースのセグメント記述子はリング3(最小特権)です。これは、ほとんどのセグメント化されたオペレーティングシステムに当てはまります。オペレーティングシステムのコアはリング0で、残りはリング3です。

Linuxカーネルは、おっしゃるように、次の4つのセグメントを設定します。

  • __KERNEL_CS(カーネルコードセグメント、base = 0、limit = 4GB、type = 10、DPL = 0)
  • __KERNEL_DS(カーネルデータセグメント、base = 0、limit = 4GB、type = 2、DPL = 0)
  • __USER_CS(ユーザーコードセグメント、base = 0、limit = 4GB、type = 10、DPL = 3)
  • __USER_DS(ユーザーデータセグメント、base = 0、limit = 4GB、type = 2、DPL = 3)

4つすべてのベースと制限は同じですが、カーネルセグメントはDPL 0、ユーザーセグメントはDPL 3、コードセグメントは実行可能で読み取り可能(書き込み不可)、データセグメントは読み取り可能で書き込み可能(実行不可)です。 。

参照:

于 2011-01-01T18:57:58.280 に答える
1

x86メモリ管理アーキテクチャは、セグメンテーションとページングの両方を使用します。非常に大まかに言えば、セグメントは、独自の保護ポリシーを持つプロセスのアドレス空間のパーティションです。そのため、x86アーキテクチャでは、プロセスが認識するメモリアドレスの範囲を複数の連続したセグメントに分割し、それぞれに異なる保護モードを割り当てることができます。ページングは​​、プロセスのアドレス空間の小さな(通常は4KB)領域を実際の物理メモリのチャンクにマッピングするための手法です。したがって、ページングは​​、セグメント内の領域が物理RAMにマップされる方法を制御します。

すべてのプロセスには2つのセグメントがあります。

  1. プログラムのコード、静的データ、ヒープ、スタックなどのユーザーレベルのプロセス固有のデータ用の1つのセグメント(アドレス0x00000000から0xBFFFFFFF)。すべてのプロセスには、独自の独立したユーザーセグメントがあります。

  2. 1つのセグメント(アドレス0xC0000000から0xFFFFFFFF)には、カーネル命令、データ、カーネルコードを実行できるいくつかのスタックなどのカーネル固有のデータが含まれ、さらに興味深いことに、このセグメントの領域は物理メモリに直接マップされます。カーネルは、アドレス変換について心配することなく、物理メモリの場所に直接アクセスできます。同じカーネルセグメントがすべてのプロセスにマップされますが、プロセスは保護されたカーネルモードで実行している場合にのみアクセスできます。

したがって、ユーザーモードでは、プロセスは0xC0000000未満のアドレスにのみアクセスできます。これより高いアドレスにアクセスすると、障害が発生します。ただし、ユーザーモードプロセスがカーネルで実行を開始すると(たとえば、システムコールを行った後)、CPUの保護ビットがスーパーバイザーモードに変更されます(一部のセグメンテーションレジスタが変更されます)。つまり、プロセスは次のようになります。これにより、0xC0000000を超えるアドレスにアクセスできます。

参照元:こちら

于 2011-01-01T18:49:10.220 に答える
0

X86の場合-Linuxセグメントレジスタはバッファオーバーフローチェックに使用されます[スタック内のいくつかのchar配列を定義した以下のコードスニペットを参照してください]:

static void
printint(int xx, int base, int sgn)
{
    char digits[] = "0123456789ABCDEF";
    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]);
}

ここで、gccで生成されたコードの逆アセンブリが表示された場合。

関数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 <__stack_chk_fail@plt>
   0x000000000040069c <+246>:   leaveq 
   0x000000000040069d <+247>:   retq 

この関数からスタックベースのchar配列を削除すると、gccはこのガードチェックを生成しません。

カーネルモジュールの場合でも、gccによって同じものが生成されるのを見てきました。基本的に、いくつかのカーネルコードをボトラップしているときにクラッシュが発生し、仮想アドレス0x28で障害が発生していました。後で、スタックポインタを正しく初期化し、プログラムを正しくロードしたと思ったのですが、gdtに正しいエントリがないため、fsベースのオフセットが有効な仮想アドレスに変換されます。

ただし、カーネルコードの場合は、__ stack_chk_fail @ plt>のようなものにジャンプする代わりに、エラーを単に無視していました。

このガードをgccに追加する関連コンパイラオプションは-fstack-protectorです。これはデフォルトで有効になっていて、ユーザーアプリをコンパイルしていると思います。

カーネルの場合、configCC_STACKPROTECTORオプションを使用してこのgccフラグを有効にできます。

構成CC_STACKPROTECTOR
 699 bool「-fstack-protectorバッファオーバーフロー検出を有効にする(実験的)」
 700はSUPERH32に依存します
 701ヘルプ
 702このオプションは、-fstack-protectorGCC機能をオンにします。これ
 703機能は、関数の先頭にカナリア値を置きます
 704リターンアドレスの直前のスタック、および検証
 705実際に戻る直前の値。スタックベースのバッファ
 706オーバーフロー(このリターンアドレスを上書きする必要がある)も
 707カナリアを上書きします。カナリアは検出され、攻撃が行われます。
 カーネルパニックを介して708が中和されました。
 709
 710この機能には、gccバージョン4.2以降が必要です。

このgs/fsがlinux/arch / x86 / include / asm/stackprotector.hである関連カーネルファイル

于 2014-03-18T10:09:06.323 に答える
-2

カーネルメモリは、ユーザースペースで実行されているプログラムから読み取ることはできません。

多くの場合、プログラムデータは実行可能ではありません(DEP、オーバーフローしたバッファやその他の悪意のある攻撃の実行を防ぐのに役立つプロセッサ機能)。

それはすべてアクセス制御に関するものです-異なるセグメントは異なる権利を持っています。そのため、間違ったセグメントにアクセスすると「セグメンテーション違反」が発生します。

于 2011-01-01T18:19:24.780 に答える