内核与应用的分离(中)

问题

内核与应用真的分离开了吗?

下面的代码合理吗?运行后会发生什么?

 0xE000是内核地址空间,应用程序通过这个地址向内核写数据,这显然是不合理的,但程序还是正常的运行了,并没有出异常。

当前设计的缺陷

系统基于平坦内存模型 (内核与应用均可访问内存的任意角落)

内核与应用执行于不同的特权级,但未利用特权级保护机制

因此,应用程序可以"恶意"修改内核空间,进而导致整个操作系统奔溃

如何改进?

需求:当应用程序对内核空间写入数据时,直接将应用程序强行结束。

解决思路

启动虚存机制 (页式内存管理) 对内存区域设置属性

利用内核与应用特权级的不同:

  • 内核 (DPL0) => 可读写内存任意区域
  • 应用 (DPL3) => 不可写入内核所在区域

具体实现方案

1. 以线性方式建立页表

  • 即:y = f(x) = x,其中,x为虚地址,y为实地址

2. 在内核中修改页表属性

  • 使得内核空间在3特权级无法写入

3. 在第一个任务执行前启动页表机制

再论页表属性

由于物理页面的地址必须按照 4K 字节对齐

页表项可使用低12位描述内存所具备的属性

只读内存页的进一步说明

CR0 寄存器中的 WP 位 (bit16) 用于全局控制内存也是否可写入

当 R/W = 0时

  • 若 CR0.WP = 1,则:任何特权级都无法对内存页写数据
  • 若 CR0.WP = 0,则:0,1,2 特权级下可对内存页写数据

修改页表属性

 BaseOfApp / 0x1000 - 1 算出内核的最后一个页面,然后将页面 0 - 页面 index 的页属性的 R/W 位设置为0,在3特权级下就不能对内核所在的内存空间进行写操作。

启动虚存机制

loader.asm

​

%include "blfunc.asm"

%include "common.asm"



org BaseOfLoader



BaseOfStack    equ    BaseOfLoader



Kernel db  "KERNEL     "

KnlLen equ ($-Kernel)

App    db  "APP        "

AppLen equ ($-App)



[section .gdt]

; GDT definition

;                                       Base,         Limit,                Attribute

GDT_ENTRY            :     Descriptor    0,            0,                   0

CODE32_DESC          :     Descriptor    0,            Code32SegLen - 1,    DA_C + DA_32 + DA_DPL0

VIDEO_DESC           :     Descriptor    0xB8000,      0x07FFF,             DA_DRWA + DA_32 + DA_DPL0

CODE32_FLAT_DESC     :     Descriptor    0,            0xFFFFF,             DA_C + DA_32 + DA_DPL0

DATA32_FLAT_DESC     :     Descriptor    0,            0xFFFFF,             DA_DRW + DA_32 + DA_DPL0

TASK_LDT_DESC        :     Descriptor    0,            0,                   0

TASK_TSS_DESC        :     Descriptor    0,            0,                   0

PAGE_DIR_DESC        :     Descriptor    PageDirBase,  4095,                DA_32 + DA_DRW 

PAGE_TBL_DESC        :     Descriptor    PageTblBase,  1023,                DA_32 + DA_DRW + DA_LIMIT_4K

; GDT end



GdtLen    equ   $ - GDT_ENTRY



GdtPtr:

          dw   GdtLen - 1

          dd   0

          

          

; GDT Selector

Code32Selector        equ (0x0001 << 3) + SA_TIG + SA_RPL0

VideoSelector         equ (0x0002 << 3) + SA_TIG + SA_RPL0

Code32FlatSelector    equ (0x0003 << 3) + SA_TIG + SA_RPL0

Data32FlatSelector    equ (0x0004 << 3) + SA_TIG + SA_RPL0

PageDirSelector       equ (0x0007 << 3) + SA_TIG + SA_RPL0

PageTblSelector       equ (0x0008 << 3) + SA_TIG + SA_RPL0





; end of [section .gdt]



[section .idt]

align 32

[bits 32]

IDT_ENTRY:

; IDT definition

;                        Selector,             Offset,       DCount,    Attribute

%rep 256

              Gate      Code32Selector,    DefaultHandler,   0,         DA_386IGate + DA_DPL0

%endrep



IdtLen    equ    $ - IDT_ENTRY



IdtPtr:

          dw    IdtLen - 1

          dd    0



; end of [section .idt]



[section .s16]

[bits 16]

BLMain:

    mov ax, cs

    mov ds, ax

    mov es, ax

    mov ss, ax

    mov sp, SPInitValue

    

    ; initialize GDT for 32 bits code segment

    mov esi, CODE32_SEGMENT

    mov edi, CODE32_DESC

    

    call InitDescItem

    

    ; initialize GDT pointer struct

    mov eax, 0

    mov ax, ds

    shl eax, 4

    add eax, GDT_ENTRY

    mov dword [GdtPtr + 2], eax

    

    ; initialize IDT pointer struct

    mov eax, 0

    mov ax, ds

    shl eax, 4

    add eax, IDT_ENTRY

    mov dword [IdtPtr + 2], eax

    

    ; load app

    push Buffer

	push BaseOfApp / 0x10

	push BaseOfApp

	push AppLen

	push App

	

    call LoadTarget

	

	add sp, 10

	

	cmp dx, 0

	jz AppErr

    

    ; reset es register

    mov ax, cs

    mov es, ax

    

    ; load kernel

    push Buffer

	push BaseOfKernel / 0x10

	push BaseOfKernel

	push KnlLen

	push Kernel

	

    call LoadTarget

	

	add sp, 10

	

	cmp dx, 0

	jz KernelErr

	

	call StoreGlobal



    ; 1. load GDT

    lgdt [GdtPtr]

    

    ; 2. close interrupt

    ;    load IDT

    ;    set IOPL to 3

    cli 

    

    lidt [IdtPtr]

    

    pushf

    pop eax

    

    or eax, 0x3000

    

    push eax

    popf

    

    ; 3. open A20

    in al, 0x92

    or al, 00000010b

    out 0x92, al

    

    ; 4. enter protect mode

    mov eax, cr0

    or eax, 0x01

    mov cr0, eax

    

    ; 5. jump to 32 bits code

    jmp dword Code32Selector : 0



KernelErr:

	mov bp, NoKernel

    mov cx, NKLen

    jmp output

    

AppErr:

	mov bp, NoApp

    mov cx, NALen

    jmp output



output:	

	mov ax, cs

	mov es, ax	

	

	mov dx, 0

    mov ax, 0x1301

	mov bx, 0x0007

	int 0x10

	

	jmp $



; esi    --> code segment label

; edi    --> descriptor label

InitDescItem:

    push eax



    mov eax, 0

    mov ax, cs

    shl eax, 4

    add eax, esi

    mov word [edi + 2], ax

    shr eax, 16

    mov byte [edi + 4], al

    mov byte [edi + 7], ah

    

    pop eax

    

    ret



;

;

StoreGlobal:

    mov dword [RunTaskEntry], RunTask

    mov dword [LoadTaskEntry], LoadTask

    mov dword [InitInterruptEntry], InitInterrupt

    mov dword [EnableTimerEntry], EnableTimer

    mov dword [SendEOIEntry], SendEOI

    

    mov eax, dword [GdtPtr + 2]

    mov dword [GdtEntry], eax

    

    mov dword [GdtSize], GdtLen / 8

    

    mov eax, dword [IdtPtr + 2]

    mov dword [IdtEntry], eax

    

    mov dword [IdtSize], IdtLen / 8

    

    ret



[section .sfunc]

[bits 32]

;

;

Delay:

    %rep 5

    nop

    %endrep

    ret

    

;

;

Init8259A:

    push ax

    

    ; master

    ; ICW1

    mov al, 00010001B

    out MASTER_ICW1_PORT, al

    

    call Delay

    

    ; ICW2

    mov al, 0x20

    out MASTER_ICW2_PORT, al

    

    call Delay

    

    ; ICW3

    mov al, 00000100B

    out MASTER_ICW3_PORT, al

    

    call Delay

    

    ; ICW4

    mov al, 00010001B

    out MASTER_ICW4_PORT, al

    

    call Delay

    

    ; slave

    ; ICW1

    mov al, 00010001B

    out SLAVE_ICW1_PORT, al

    

    call Delay

    

    ; ICW2

    mov al, 0x28

    out SLAVE_ICW2_PORT, al

    

    call Delay

    

    ; ICW3

    mov al, 00000010B

    out SLAVE_ICW3_PORT, al

    

    call Delay

    

    ; ICW4

    mov al, 00000001B

    out SLAVE_ICW4_PORT, al

    

    call Delay

    

    pop ax

    

    ret

    

; al --> IMR register value

; dx --> 8259A port

WriteIMR:

    out dx, al

    call Delay

    ret

    

; dx --> 8259A

; return:

;     ax --> IMR register value

ReadIMR:

    in ax, dx

    call Delay

    ret



;

; dx --> 8259A port

WriteEOI:

    push ax

    

    mov al, 0x20

    out dx, al

    

    call Delay

    

    pop ax

    

    ret



[section .gfunc]

[bits 32]

;

;  parameter  ===> Task* pt

RunTask:

    push ebp

    mov ebp, esp

    

    mov esp, [ebp + 8]

    

    lldt word [esp + 96]

    ltr word [esp + 98]

    

    pop gs

    pop fs

    pop es

    pop ds

    

    popad

    

    add esp, 4

    

    mov dx, MASTER_IMR_PORT

    in ax, dx

    

    %rep 5

    nop

    %endrep

    

    and ax, 0xFE

    out dx, al

    

    %rep 5

    nop

    %endrep

    

    mov eax, PageDirBase

	mov cr3, eax

	mov eax, cr0

	or  eax, 0x80000000

	mov cr0, eax

    

    iret

    

; void LoadTask(Task* pt);

;

LoadTask:

    push ebp

    mov ebp, esp

    

    mov eax, [ebp + 8]

    

    lldt word [eax + 96]

    

    leave

    

    ret

       

;

;

InitInterrupt:

    push ebp

    mov ebp, esp

    

    push ax

    push dx

    

    call Init8259A

    

    sti

    

    mov ax, 0xFF

    mov dx, MASTER_IMR_PORT

    

    call WriteIMR

    

    mov ax, 0xFF

    mov dx, SLAVE_IMR_PORT

    

    call WriteIMR

    

    pop dx

    pop ax

    

    leave 

    ret



;

;

EnableTimer:

    push ebp

    mov ebp, esp

    

    push ax

    push dx

    

    mov dx, MASTER_IMR_PORT

    

    call ReadIMR

    

    and ax, 0xFE

    

    call WriteIMR

    

    pop dx

    pop ax

    

    leave

    ret  

    

; void SendEOI(uint port);

;    port ==> 8259A port

SendEOI:

    push ebp

    mov ebp, esp

    

    mov edx, [ebp + 8]

    

    mov al, 0x20

    out dx, al

    

    call Delay

    

    leave

    ret 

    

[section .s32]

[bits 32]

CODE32_SEGMENT:

    mov ax, VideoSelector

    mov gs, ax

    

    mov ax, Data32FlatSelector

    mov ds, ax

    mov es, ax

    mov fs, ax

    

    mov ax, Data32FlatSelector

    mov ss, ax

    mov esp, BaseOfLoader

    

    call SetupPage

    

    jmp dword Code32FlatSelector : BaseOfKernel





;

;

SetupPage:

	push es

	push eax

	push ecx

	push edi	



	mov ax, PageDirSelector

	mov es, ax

	mov edi, 0

	mov ecx, 1024   ;  1K sub page tables

	mov eax, PageTblBase | PG_P | PG_USU | PG_RWW



	cld



stdir:

	stosd

	add eax, 4096

	loop stdir



	mov ax, PageTblSelector

	mov es, ax

	mov edi, 0

	mov ecx, 1024 * 1024     ; 1M pages

	mov eax, PG_P | PG_USU | PG_RWW



	cld



sttbl:

	stosd

	add eax, 4096

	loop sttbl	



	pop edi

	pop ecx

	pop eax

	pop es



	ret	

    

;

;

DefaultHandlerFunc:

    iret

    

DefaultHandler    equ    DefaultHandlerFunc - $$



Code32SegLen    equ    $ - CODE32_SEGMENT



NoKernel db  "No KERNEL"	

NKLen    equ ($-NoKernel)

NoApp    db  "No KERNEL"	

NALen    equ ($-NoApp)



Buffer db  0

​

common.asm

; Page Base
PageDirBase    equ    0x50000
PageTblBase    equ    0x51000

const.h

#define PageDirBase     0x50000
#define PageTblBase     0x51000

kernel.c

void ConfigPageTable()
{
	uint* TblBase = (uint*)PageTblBase;
	uint index = BaseOfApp / 0x1000 - 1;
	uint i = 0;
	
	for(i = 0; i <= index; i++)
	{
		uint* addr = TblBase + i;
		uint value = *addr;
		
		value &= 0xFFFFFFFD;
		*addr = value;
	}
}

task.c

#include "utility.h"
#include "task.h"
#include "app.h"

#define MAX_TASK_NUM      4
#define MAX_RUNNING_TASK  2
#define MAX_READY_TASK    (MAX_TASK_NUM - MAX_RUNNING_TASK)

static AppInfo* (*GetAppToRun)(uint index) = NULL;
static uint (*GetAppNum)() = NULL;

void (* const RunTask)(volatile Task* pt) = NULL;
void (* const LoadTask)(volatile Task* pt) = NULL;

volatile Task* gCTaskAddr = NULL;
// static TaskNode gTaskBuff[MAX_TASK_NUM] = {0};
static TaskNode* gTaskBuff = NULL;
static TSS gTSS = {0};
static Queue gFreeTaskNode = {0};
static Queue gReadyTask = {0};
static Queue gRunningTask = {0};
static Queue gWaitingTask = {0};
// static TaskNode gIdleTask = {0};
static TaskNode* gIdleTask = NULL;
static uint gAppToRunIndex = 0;
static uint gPid = 10;

void TaskEntry()
{
	if(gCTaskAddr != NULL)
	{
		gCTaskAddr->tmain();
	}
	
	// to destory current task here
	asm volatile(
	    "movw $0, %ax \n"
	    "int $0x80 \n"
	);
	
	while(1);    // TODO: schedule next task to run
}

void IdleTask()
{  
    while(1);
}

static void InitTask(Task* pt, uint id, const char* name, void(*entry)(), ushort pri)
{
    pt->rv.cs = LDT_CODE32_SELECTOR;
    pt->rv.gs = LDT_VIDEO_SELECTOR;
    pt->rv.ds = LDT_DATA32_SELECTOR;
    pt->rv.es = LDT_DATA32_SELECTOR;
    pt->rv.fs = LDT_DATA32_SELECTOR;
    pt->rv.ss = LDT_DATA32_SELECTOR;
    
    pt->rv.esp = (uint)pt->stack + sizeof(pt->stack);
    pt->rv.eip = (uint)TaskEntry;
    pt->rv.eflags = 0x3202;
    
    pt->id = id;
    
    StrCpy(pt->name, name, sizeof(pt->name) - 1);
    
    pt->tmain = entry;
    
    pt->current = 0;
    pt->total = 256 - pri;
    
    gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
    gTSS.esp0 = (uint)&pt->rv + sizeof(pt->rv);
    gTSS.iomb = sizeof(TSS);
    
    SetDescValue(AddrOff(pt->ldt, LDT_VIDEO_INDEX),  0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3);
    SetDescValue(AddrOff(pt->ldt, LDT_CODE32_INDEX), 0x00,    0xFFFFF, DA_C + DA_32 + DA_DPL3);
    SetDescValue(AddrOff(pt->ldt, LDT_DATA32_INDEX), 0x00,    0xFFFFF, DA_DRW + DA_32 + DA_DPL3);
    
    pt->ldtSelector = GDT_TASK_LDT_SELECTOR;
    pt->tssSelector = GDT_TASK_TSS_SELECTOR;
}

static void PrepareForRun(volatile Task* pt)
{
	pt->current++;

	gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
    gTSS.esp0 = (uint)&pt->rv + sizeof(pt->rv);
    gTSS.iomb = sizeof(TSS);
    
    SetDescValue(AddrOff(gGdtInfo.entry, GDT_TASK_LDT_INDEX), (uint)&pt->ldt, sizeof(pt->ldt)-1, DA_LDT + DA_DPL0);
}

static void CreateTask()
{
	int num = GetAppNum();
	
	while((gAppToRunIndex < num) && (Queue_Length(&gReadyTask) < MAX_READY_TASK))
	{
		TaskNode* tn = (TaskNode*)Queue_Remove(&gFreeTaskNode);
		
		if(tn != NULL)
		{
			AppInfo* app = GetAppToRun(gAppToRunIndex);
			
			InitTask(&tn->task, gPid++, app->name, app->tmain, app->priority);
			
			Queue_Add(&gReadyTask, &tn->head);	
		}
		else
		{
			break;
		}
		
		gAppToRunIndex++;
	}
}

static void ReadyToRunning()
{
	QueueNode* node = NULL;

	if(Queue_Length(&gReadyTask) < MAX_READY_TASK)
	{
		CreateTask();
	}
	
	while((Queue_Length(&gReadyTask) > 0) && (Queue_Length(&gRunningTask) < MAX_RUNNING_TASK))
	{
		node = Queue_Remove(&gReadyTask);
		
		((TaskNode*)node)->task.current = 0;
		
		Queue_Add(&gRunningTask, node);
	}
}

static void RunningToReady()
{
	if(Queue_Length(&gRunningTask) > 0)
	{
		TaskNode* tn = (TaskNode*)Queue_Front(&gRunningTask);
	
		if(!IsEqual(tn, gIdleTask))
		{
			if(tn->task.current == tn->task.total)
			{
				Queue_Remove(&gRunningTask);
				Queue_Add(&gReadyTask, (QueueNode*)tn);
			}
		}
	}
}

static void CheckRunningTask()
{
	if(Queue_Length(&gRunningTask) == 0)
	{
		Queue_Add(&gRunningTask, &gIdleTask->head);
	}
	else if(Queue_Length(&gRunningTask) > 1)
	{
		if(Queue_Front(&gRunningTask) == &gIdleTask->head)
		{
			Queue_Remove(&gRunningTask);
		}
	}
}

void TaskModInit()
{
	int i = 0;
    
    gTaskBuff = (void*)0x20000;
    gIdleTask = (void*)AddrOff(gTaskBuff, MAX_TASK_NUM);
    
    GetAppToRun = (void*)(*((uint*)GetAppToRunEntry));
    GetAppNum = (void*)(*((uint*)GetAppNumEntry));
    
    Queue_Init(&gFreeTaskNode);
    Queue_Init(&gReadyTask);
    Queue_Init(&gRunningTask);
    Queue_Init(&gWaitingTask);
    
    for(i = 0; i < MAX_TASK_NUM; i++)
    {
    	Queue_Add(&gFreeTaskNode, (QueueNode*)AddrOff(gTaskBuff, i));
    }
    
    SetDescValue(AddrOff(gGdtInfo.entry, GDT_TASK_TSS_INDEX), (uint)&gTSS, sizeof(gTSS)-1, DA_386TSS + DA_DPL0);
    
    InitTask(&(gIdleTask->task), 0, "IdleTask", IdleTask, 255);
    
    ReadyToRunning();
    CheckRunningTask();
    
}

void LaunchTask()
{
    gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
    
    PrepareForRun(gCTaskAddr);
    
    RunTask(gCTaskAddr);
}

void Schedule()
{
	RunningToReady();
	
	ReadyToRunning();
	
	CheckRunningTask();

	Queue_Rotate(&gRunningTask);
	
    gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
    
    PrepareForRun(gCTaskAddr);
    
    LoadTask(gCTaskAddr);
}

void KillTask()
{
	QueueNode* node = Queue_Remove(&gRunningTask);
	
	Queue_Add(&gFreeTaskNode, node);
	
	Schedule();
}

kamin.c

#include "task.h"
#include "interrupt.h"
#include "screen.h"

void KMain()
{
	void (*AppModInit)() = (void*)BaseOfApp;

    int n = PrintString("D.T.OS\n");
    
    PrintString("GDT Entry: ");
    PrintIntHex((uint)gGdtInfo.entry);
    PrintChar('\n');
    
    PrintString("GDT Size: ");
    PrintIntDec((uint)gGdtInfo.size);
    PrintChar('\n');
    
    PrintString("IDT Entry: ");
    PrintIntHex((uint)gIdtInfo.entry);
    PrintChar('\n');
    
    PrintString("IDT Size: ");
    PrintIntDec((uint)gIdtInfo.size);
    PrintChar('\n');
    
    AppModInit();
    
    TaskModInit();
    
    IntModInit();
    
    ConfigPageTable();
    
    LaunchTask();
    
}

由于我们要进行内存保护,所以要开启内存分页机制。

我们在 common.asm 中定义了 页目录的起始地址为 0x50000,子页表的起始地址为 0x51000。

在 loader.asm 中,我们定义了页目录和子页表的全局段描述符和选择子,并且定义了 SetupPage 函数,这个函数初始化页目录和子页表,以线性方式初始化页表。并且在执行任务前开启内存分页机制。

在 kernel.c 中,我们定义了 ConfigPageTable 函数,这个函数先将内核最后的内存页 index 计算出来,然后将内存页 0 - 内存页 index 的页属性 R/W位 (第1位) 设置为0,即:在特权级为3时,不能对物理页进行写操作。

我们在 kmian.c 中调用 ConfigPageTable 函数,但当应用程序没有向内核使用的内存写数据时,仍然发生异常。

我们找到了问题的原因:TaskEntry 是每个任务函数的入口,进入这个函数时,特权级就从0变为3了,在 TaskEntry 函数中,又会调用 tmain 函数,这就会使用栈,而使用的这个栈,我们是定义在 task.c 中,这个栈所使用的内存范围是在内核的内存范围内的,所以我们就得将每个 TaskNode 的内存放在应用程序的内存当中,我们就定义 TaskNode 的指针,指向应用程序所使用的内存空间,这样应用程序在进行函数调用时,修改的就是应用程序的内存空间,而不是内核的内存空间了。

我们并没有设置 CR0 的 WP 位,是因为它的初始值就为0。

 现在如果应用程序想要向内核所在的区域写入数据,就会出现异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值