Voor de gevorderde coder

Pagina: 1
Acties:
  • 356 views sinds 30-01-2008

  • JayTaph
  • Registratie: Oktober 1999
  • Laatst online: 28-11-2025

JayTaph

Portability is for canoes.

Topicstarter
Een tijdje geleden was er een
interessant topic over buffer overflows en exploits.


Nou denk ik dat er best wel een aantal mensen zijn die hier over kunnen meepraten, dus heb ik een soort van proposal gemaakt over een toekomstige 'feature' in een compiler die straks geport gaat worden naar een specifiek OS toe (de mijne :)). Wat ik een beetje van plan was, is om te kijken of het uberhaupt zo moeilijk is om via een compiler al een heleboel exploits buiten de deur te houden en naar mijn mening kan dat best.

Daarom heb ik dus een proposal gemaakt hierover en aangezien der best wel wat fouten her en der in zitten en er over heel wat dingen nog gediscussieerd kunnen worden gooi ik em maar bij deze online..




Proposition "safe mode" option in CybOS compilers
-------------------------------------------------

Especially in the times of persistent connections between many computers and users, the need for tense security is rising every day. Not only the need for better software is needed, but also the need for better tools to create these software is a very important part of the security-measurements that should be taken. For instance, if a application like a java engine has a serious exploit, the software running on it can be 100% secure, but will still hold the exploits from the underlying application. This works it's way right down to the hardware levels of the computer system.

After spending some time in the world of exploits and protection against them, I noticed that almost all exploits are resulting from bad programming, wether or not intentionally. A simple program that doesn't check the size of incoming data is a very good candidate for a "buffer overflow"-exploit.

A "buffer overflow"-exploit is simply a case of overflowing the input buffer in a way, it changes the normal execution of the program into the execution of code which is passed in the buffer (which overflowed). With the right factors, it's not very hard to create your own code which is run by the remote machine by "dropping the code inside an egg" (ie: eggdropping the code into a buffer-overflow).

In todays protected kernel environments where all programs run in heavily protected area's so they cannot harm other program or either the kernel itself, it's not very logical that the OS would allow the programs to hurt themselves. Afterall, the propositions I will make aren't very hard to implement, yet somehow, nobody actually implemented it inside there compilers.

JayTaph






The theory behind "safe mode"
-----------------------------
Before you can actually read this chapter, it is highly recommended that you know your intel 386+ PMODE theory.

How does a normal program (or process) execute in a protected kernel environment:
1. The kernel allocate memory to store the process.
2. The kernel initialises a task (including TSS) for the scheduler.
3. The kernel creates a LDT for the new process.
4. The kernel copies the pages it needs into the memory and sets the process as runnable.
5. The scheduler will run the process when appropriate.

The catch in this "initialision"-flow is the creation of the LDT. A standard LDT consists of 2 descriptors:

1. code (readonly) segment, base <0>, limit <codelimit>+<datalimit>+<stacklimit>
2. data (r/w) segment, base <0>, limit <codelimit>+<datalimit>+<stacklimit>

The stack of the program will be placed at the top of the datalimit. For instance, the selectors upon entry of the process could be:

(code (.text) size 10000)
(data (.data) size 10000)
( (.bss ) size 10000)
(stack (.stack) size 10000)

CS: 000Fh (index 8 + ring 3 + LDT) (limit = 10000)
DS: 0017h (index 16 + ring 3 + LDT) (limit = 50000)
SS: 0017h (index 16 + ring 3 + LDT) (limit = 50000)
SP: 050000h


Two questions pops into mind when viewing the descriptors and selectors:

1. Why is the base and limit of the code the same as the base of the data?
Not sure. It's very possible that it doesn't have to be that way. Data doesn't need to interfere with the code, only the code can interfere with the data. When placing the code inside a protected area of the memory, there is no possibility for anything to overflow data in order to run code. However, it isn't possible anymore to run self-modifying code in the general way.

2. Why is the stack in the same descriptor as the data?
Most languages uses the stack to pass variables from one function to another, and to declare temporary values used in the function:
code:
1
2
3
4
5
6
7
8
int global_data = 0;

void test_function (int param1) {
  int i;

  i = param1;
  i = global_data;
}

In this code-snippet, there are 3 variables: global_data which is located in the .data segment. the param1 which is located on the stack and i which is also located on the stack.

When entering test_function, the BasePointer (EBP) will store the current stackpointer (ESP). After that, room for the temporary data will be reserved (in this case, 4 bytes for the integer "i". Everything BELOW EBP are the temporary vars while everything above points to the parameter list of the function.

The line "i = param1", which copies a variable to another variable actually copies 2 variables on the stack ( mov eax, [ebp+4]; mov [ebp-8], eax), while the other line "i = global_data" copies from the data-segment to the stack: mov eax, [global_data]; mov [ebp-8], eax). As you might know, ebp and esp uses SS and normal data references are done in DS.

There should be no need for the stack and data to be the same memory area, also, there should be no need for the code to be in the same area as the data or stack.





Buffer overflows
----------------

Standard overflows are created by modifing the return address of a function by changing it into a own function which execve's a shell.

1. Pfunction
A Pfunction is a protected function. This means the function gets another stack than the original one from the program. This means the original stack which holds the return address and the saved stackpointer, while the 'stackcopy' holds the data and the temporary vars. Exploits which rely on modifying the EIP don't work anymore since there is no EIP to modify :)

A normal stack will hold the following items inside a function:
code:
1
2
3
4
5
6
7
int blaat (void)
{
  int tmp[3];
  int i;

  return 0;
}

RETURN ADDRESS (EIP)
STACKPOINTER (EBP)
TMP [3]
TMP [2]
TMP [1]
I



So when we overwrite TMP[5], we actually are overwriting the EIP value. When we change this into the addres of another function, lets say: tmp[5]=&exploit, we could do some serious damage (in case of stack-data can also be executed by the program).

A Pfunction does something else with the stack:

1. 'CALL' to the Pfunction by placing EIP on the stack and loading the new EIP.
2. Save original EBP and copy the current ESP into EBP
3. No, create a new stack-descriptor, the base of the stack is ESP+8 and the limit is the 'precalculated' size of the stack by the program (parameter-size + variable-size).
4. Load the new stack selector and load the ESP on the start of the global vars:

if we have 1 int and 1 char on the paramlist, we load ESP with 8. This is because we should only load 5 (4 bytes for the int, 1 byte for the char), but the stack is DWORD aligned, so 8 bytes it is..
if we have 1 integer as temporary var (4 bytes), the limit of the descriptor should be 12 (8 param, 2 var)

dword [SS:ESP-8] points to the integer,
byte [SS:ESP-4] points to the char,
dword [SS:ESP+0] points to the temporary integer


The footer of a function should do:
1. Delete stack descriptor.
2. Restore original SS and ESP
3. Restore EBP
4. RET to the EIP


Pre:
1. No more manipulation of the stack.

Con:
1. Creating a selector on the fly PER function is very slow and therefor should only be done by critical functions like input-function. There isn't really any reason to use a Pfunction on simple calculation functions or functions which are run very often.



Pfunctions can be created by the using the keyword __PROTECTED__

char * __PROTECTED__ GetInput (void);



2. Pchar

A Pchar is basicly the same as a Pfunction, only it is data type, not a function. When using for instance: PChar tmp[10], the char array is setup as a fresh descriptor which has a base &tmp and a limit 10 (and not 12). References to a PCHAR are always based from offset 0 and the data segment (possible the ES, GS or FS can be used).


Flow of defining a PCHAR:
code:
1
2
3
4
5
6
7
8
9
10
void main (void)
{
  int i;
  char tmp[10];

  for (i=0; i!=11; i++)
  {
    tmp[i]='A';         // Uh Oh
  }
}

The DJGPP GCC compiler output of "gcc -S test.c" returns test.S:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
        .file   "test.c"
gcc2_compiled.:
___gnu_compiled_c:
.text
        .p2align 2
.globl _main
_main:
        pushl %ebp          ; Setup stack frame
        movl %esp,%ebp
        subl $24,%esp           ; Reserve mem
        nop
        movl $0,-4(%ebp)        ; -4(ebp)=i  i=0;
        .p2align 4,,7
L3:
        cmpl $11,-4(%ebp)       ; i==11?
        jne L6              ; No .. jmp L6
        jmp L4              ; Otherwise done
        .p2align 4,,7
L6:
        leal -16(%ebp),%eax     ; eax = -16(ebp) = base of TMP
        movl -4(%ebp),%edx      ; edx = i
        movb $65,(%edx,%eax)        ; mov 'A'  eax+edx (tmp[i])
L5:
        incl -4(%ebp)           ; increase i
        jmp L3              ; jmp to the 'for'-loop
        .p2align 4,,7
L4:
L2:
        movl %ebp,%esp          ; Restore stackspace
        popl %ebp           ; Restore EBP
        ret             ; return (to stub/end of program)

Now we change the code so it uses Pchar:
code:
1
2
3
4
5
6
7
8
9
10
void main (void)
{
  int i;
  pchar tmp[10];

  for (i=0; i!=11; i++)
  {
    tmp[i]='A';         // Uh Oh
  }
}

When using a Pchar, the compiler allocated memory and defines the descriptors trough the normal system calls:

System call interrupt = 0x80

Allocate Memory:
AH = 20h
AL = Granularity (0 = ECX is in bytes, 1 = ECX is in 4K pages)
ECX = size of allocation

Return:
EAX = size of allocation (depending on granularity)
EBX = base address of the allocation


Allocate Descriptor
AH = 40h
AL = Granularity (0 = ECX is in bytes, 1 = ECX is in 4K pages)
EBX = base address
ECX = limit

Return:
AX = selector

Free Descriptor
AH = 41h
BX = Selector






The (future) CCC (CybOS C Compiler) output of "ccc -S test.c" returns test.S:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
        .file   "test.c"
gcc2_compiled.:
___gnu_compiled_c:
.text
        .p2align 2
.globl _main
_main:
        pushl %ebp          ; Setup stack frame
        movl %esp,%ebp
        subl $24,%esp           ; Reserve mem   (why is it always 24?)
        nop

(*) movl $10, %ecx          ; Size of allocation
(*) movl $2000, %eax        ; Service Nr and granularity
(*) int  $0x80
                    ; EBX already filled
(*) movl $10, %ecx          ; Size of allocation
(*) movl $4000, %eax        ; Service Nr and granularity
(*) int  $0x80  
(*) movw %ax, (_main.tmp_slctr) ; Place selector into global data defined by the compiler

        movl $0,-4(%ebp)        ; -4(ebp)=i  i=0;

        .p2align 4,,7
L3:
        cmpl $11,-4(%ebp)       ; i==11?
        jne L6              ; No .. jmp L6
        jmp L4              ; Otherwise done
        .p2align 4,,7
L6:
(*) movw %fs, (_main.tmp_slctr) ; Load FS with selector
        movl -4(%ebp),%edx      ; edx = i
(*)     movb $65,fs:(%edx)      ; mov 'A' into tmp[i]
L5:
        incl -4(%ebp)           ; increase i
        jmp L3              ; jmp to the 'for'-loop
        .p2align 4,,7
L4:
L2:

(*) movw (_main.tmp_slctr), %bx ; Load selector into BX
(*) movl $4100, %eax        ; Service Nr
(*) int  $0x80          ; Remove descriptor

        movl %ebp,%esp          ; Restore stackspace
        popl %ebp           ; Restore EBP
        ret             ; return (to stub/end of program)

This program should not cause a overflow, but a segmentation fault. In this example, it's pretty obviously that it will overflow, but most of the times it wont because of many external factors. So in critical processes, it's better that the program shuts itself down after finding a "error", rather then letting the program continue so it can exploit itself.

Also, notice that in this example, the real delay is in the fact that in every recursion FS is loaded. A smart compiler could notice how many Pchars are used in a recursion, and when only 3 are found, FS, GS and ES could be loaded BEFORE entering the loop so you will have no penalty when using Pchars. But bare in mind: Pchars are mostly used when receiving user-data. These functions will idle a lot so you will notice no delay in that case.





Limitations of the LDT
----------------------
A local descriptor table can only store 8192 selectors. With the exception of 3 selectors (text, data, stack) this leaves 8189 selectors for Pchars and PFunction. This should be more than enough for a single program, but even when it's not, there is always the possibility to switch to another LDT. Off course, even the GDT supports 8192 descriptors (including TSS, including global descriptors) so Pchars entries will eventually exhaust. I think the changes on that are very slim.

Yo dawg, I heard you like posts so I posted below your post so you can post again.


Verwijderd

Als ik je voorstel globaal lees, dan klinkt het inderdaad erg logisch. Ik sta zelf te kijken van de buffer overflow weaknesses die bijna dagelijks worden ontdekt en zou ook zo'n feature op compilers veel eerder hebben verwacht. Misschien is er al wel eerder over gedacht, maar hebben jij en ik er nog nooit van gehoord. Heb je uitvoerig gezocht naar eventueel al bestaande projecten?

Ik ben niet het technische stuk ingedoken, want op dat level is mijn kennis niet paraat noch actueel, oftewel, daar zal ik weinig van snappen. :)

Succes verder, en mijn complimenten :)

Verwijderd

Ik denk dat je de site al kent, maar op http://immunix.org/ staat stackguard een patch op de gcc compiler die je zou moeten bescherment tegen buffer overflows.. en nog een aantal papers over buffer overflow en het voorkomen er van. maargoed ik denk dat je 'm al kent, zoniet have fun ;)

--ray

  • mth
  • Registratie: Mei 2000
  • Laatst online: 05-02-2025

mth

Voor Linux is er een kernel patch die de stack non-executable maakt. In de README daarvan staan wat details over problemen die daarbij kunnen optreden.

www.openwall.com/linux/

"This is the noise that keeps me awake."


Verwijderd

Waarom beperk je je tot pchar? Je zou je __PROTECTED__ toch voor alle datatypes kunnen gebruiken; niet alleen voor functions?

Dus:
__PROTECTED__ char tmp[10];
__PROTECTED__ int score[1000];
int __PROTECTED__ getsome(void);

- Dit werkt alleen voor talen waar de variable scoping simpel in elkaar zit. Geneste functies (zoals in bv. pascal) zijn niet meer mogelijk, want de inner-functions hebben geen toegang meer tot variabelen in de outer-function (die liggen immers op een andere stack).

- Ik denk dat dit vooral een debugging/testing feature gaat worden, de performance loss lijkt me nog al groot tov. het normale "flat-memory" model van c; vooral ook omdat je de selectors van je protected types naar elke functie moet passen die die types gebruiken. Je hebt dus niet alleen overhead van de selector creatie, maar ook nog eens die van (tragere) selector based instructies in andere functies (en grotere stacks uiteraard).

<typo>
sector != selector
</typo>

  • The - DDD
  • Registratie: Januari 2000
  • Laatst online: 17-08 22:44
:?:?

Hmm, toch maar is een keer een boek over assembler doorwerken, want nu klapperen m'n oortjes een beetje.

  • Onno
  • Registratie: Juni 1999
  • Niet online
Erg interessante ideetjes JayTaph... :)

Het nadeel is alleen dat je deze dingen actief en selectief moet toepassen. Selectief, omdat je niet oneindig veel descriptors kunt maken, maar waar baseer je je selectie dan op? Het irritante van bugs is namelijk dat je ze niet kent. Het bepalen van welke procs en/of arrays je wilt protecten is dus wat lastig. :)

Er zitten ook wat kleine foutjes in imho...
if we have 1 integer as temporary var (4 bytes), the limit of the descriptor should be 12 (8 param, 2 var)
Nee! Zo kun je namelijk geen calls meer maken! (in het algemeen: niets meer pushen)
(ik zou trouwens een expand-down segment gebruiken, en dan is de limiet dus -12 oid)
dword [SS:ESP-8] points to the integer, byte [SS:ESP-4] points to the char, dword [SS:ESP+0] points to the temporary integer
Idem.
System call interrupt = 0x80
Ik vind het gebruiken van interrupts persoonlijk erg lelijk, het brengt een inflexibiliteit met zich mee die ik niet gewenst vind. Maar da's verder nogal off-topic. :)
Off course, even the GDT supports 8192 descriptors
Neuh, 8191. :P
Why is the base and limit of the code the same as the base of the data?
Dat heb ik me ook afgevraagd... en ik doe het zelf gewoon lekker anders.
Op zich is het risico van overflows hiermee trouwens niet echt groot, je markeert pages met code immers standaard als read-only. (mag ik hopen)
Geneste functies (zoals in bv. pascal) zijn niet meer mogelijk, want de inner-functions hebben geen toegang meer tot variabelen in de outer-function (die liggen immers op een andere stack).
Neuh, dat lijkt me geen probleem. Procedures kunnen immers gewoon andere segmenten aanspreken. Moet je alleen met far pointers gaan werken. Naja... so be it.
Voor Linux is er een kernel patch die de stack non-executable maakt.
Executable maken doe je in segment descriptors. (pages kun je niet non-executable maken)
En aangezien je voor code sowieso al een andere descriptor gebruikt dan voor code, snap ik deze patch niet helemaal geloof ik... :)

Verwijderd

Op vrijdag 01 december 2000 20:45 schreef Onno het volgende:
Neuh, dat lijkt me geen probleem. Procedures kunnen immers gewoon andere segmenten aanspreken. Moet je alleen met far pointers gaan werken. Naja... so be it.
Hmm, klopt wel. Het bijhouden van de stack chains wordt wel een stuk lastiger.

Met far pointers werken moeten we sowieso (en overal). Anders kunnen we nooit meer een pchar, of om het even welke variabele die in een protected function gedeclareerd is, passen naar een andere function. (Dit bedoelde ik te zeggen met dat tweede punt uit m'n vorige posting, maar het kwam er wat ongelukkig uit.)

  • JayTaph
  • Registratie: Oktober 1999
  • Laatst online: 28-11-2025

JayTaph

Portability is for canoes.

Topicstarter
Ok.. eventjes een opmerking vooraf (naja, achteraf dus). Het zijn ideetjes die op dit moment nog niet (althans, niet direct) toegepast worden, maar wel eventueel zou kunnen. Veel methodes (waaronder de stackguard van immunix en dergelijke) zijn feitelijk een doekje voor het bloeden. Het zijn wel oplossingen, maar niet op het niveau waar het eigenlijk thuishoort.

Ik geloof ook niet dat ik nu de ultieme oplossing heb gevonden tegen overflows of stomme memoryleaks die eigenlijk nooit gevonden worden op een andere manier, maar het zijn wel basic dingen die ervoor kunnen zorgen dat een proces via een interne of externe reden zichzelf kan verwonden (met alle gevolgen van dien).
Het nadeel is alleen dat je deze dingen actief en selectief moet toepassen. Selectief, omdat je niet oneindig veel descriptors kunt maken, maar waar baseer je je selectie dan op? Het irritante van bugs is namelijk dat je ze niet kent. Het bepalen van welke procs en/of arrays je wilt protecten is dus wat lastig.
Inderdaad.. Het werkt selectief. Het beste zou zijn als elke variabele een aparte descriptor toegewezen zou krijgen, hetgeen eindigt in een overhead van 32 bytes per variabele in de LDT, laat staan het laden van elke specifieke selector. Waar de "safe mode" voor bedoelt is, is om in de eerste fase van data-invoer (hetzij via een file, hetzij via argv, hetzij via gets) een soort van hardware-matige blocking toe te passen. Mocht er dan iets fout gaan waar je als programmeur geen rekening mee hebt gehouden, dan is het altijd nog de processor die daar dan een stokje voor steekt. Hell, ik vind het een beetje onzinnig om je proc in Pmode te draaien terwijl je er geen fuck mee doet. Dan kan je net zo goed alles laten draaien in een 4GB descriptor op ring 0 bij wijze van spreken.

Maar goed, eigenlijk alles wat achter de input zit (bijvoorbeeld parsen van de strings etc) hoeft dus al niet meer protected te zijn aangezien het fysiek niet meer mogelijk is dat hier data in zit die niet zo kloppen.

De input-functies zijn meestal wel duidelijk aanwezig dus ik denk niet dat het een heel erg groot probleem zou zijn om uit te vinden welke functies en strings protected zouden moeten zijn.


>>if we have 1 integer as temporary var (4
>>bytes), the limit of the descriptor should
>>be 12 (8 param, 2 var)
>Nee! Zo kun je namelijk geen calls meer
>maken! (in het algemeen: niets meer pushen)
Ow shit.. je hebt gelijk :) De originele bedoeling van een "aparte" stack was om de return address (EIP) te beveiligen. Andere functies oproepen is op deze manier niet mogelijk, TENZIJ je op voorhand kunt bepalen hoeveel ruimte je daarvoor nodig zult hebben. Ik weet te weinig van compilers af om te weten of ze dat tijdens compile-time kunnen bereken, maar als dat zo is, dan is dat probleem opgelost. Zo niet, dan denk ik dat je de limit met rust moet laten en alleen de base moet opschuiven zodat deze niet meer in het bereik ligt van het originele return address. (Immunix doet feitelijk hetzelfde, alleen "protected" ze de EIP door er vars achter te zetten en te controleren. Vind ik persoonlijk dus een beetje een lapmiddel). Maar goed.. wel degelijk een grove fout in de tekst.

>(ik zou trouwens een expand-down segment
>gebruiken, en dan is de limiet dus -12 oid)
Ik haat expand down.. gebruik ze dan ook nooit, vandaar dat ik ze ook niet laat terugkomen in deze tekst :)


>>System call interrupt = 0x80
>Ik vind het gebruiken van interrupts
>persoonlijk erg lelijk, het brengt een
>inflexibiliteit met zich mee die ik niet
>gewenst vind. Maar da's verder nogal off-
>topic.
Maar zeker niet minder interessant. Wat eigenlijk elk OS nodig heeft, is een lowlevel functie-set die elke programma tot zijn beschikking heeft. Of dit nu een API is die meegelinkt word, of dit via een software-int gedaan wordt (Linux 0x80, MS-DOS 0x21, lijkt mij niet zoveel uit te maken). Waar zit dan die 'inflexibiliteit'?

>>Off course, even the GDT supports 8192
>>descriptors
>Neuh, 8191.
Ik gebruik de NULL descriptor niet als GDTR. Dus feitelijk wel 8192 :) Maar inderdaad, hij is compleet nutteloos aangezien op processor-niveau hier niets mee gedaan wordt. Maar ok.. 8191 [b]bruikbare[b] descriptors.


>>Why is the base and limit of the code the
>>same as the base of the data?
>Dat heb ik me ook afgevraagd... en ik doe
>het zelf gewoon lekker anders.
Same here :)

>Op zich is het risico van overflows hiermee
>trouwens niet echt groot, je markeert pages
>met code immers standaard als read-only.
>(mag ik hopen)
Inderdaad, niet alles in de proposal is bedoeld om puur overflows tegen te gaan, maar ook veelal om een goede scheiding te bepalen tussen de verschillende gebieden van een process. Net als jij dus zie ik ook het nut gewoonweg niet in van 'overlappende' descriptors dus laat ze dan ook weg is mijn mening. Ik moet eerlijk zeggen dat ik niet precies weet hoe linux zijn 'code'-pages marked, maar goed, wat dat betreft is linux op een aantal punten nu ook op de meest geweldigste manier in elkaar gezet.

>>Geneste functies (zoals in bv. pascal)
>>zijn niet meer mogelijk, want de inner-
>>functions hebben geen toegang meer tot
>>variabelen in de outer-function (die
>>liggen immers op een andere stack).
>Neuh, dat lijkt me geen probleem.
>Procedures kunnen immers gewoon andere
>segmenten aanspreken. Moet je alleen met
>far pointers gaan werken. Naja... so be it.

>>Voor Linux is er een kernel patch die de
>>stack non-executable maakt.
>Executable maken doe je in segment
>descriptors. (pages kun je niet non-
>executable maken)
>En aangezien je voor code sowieso al een
>andere descriptor gebruikt dan voor code,
>snap ik deze patch niet helemaal geloof
>ik...
Even kijken of ik het begrijp. Wat een 'normale' eggdrop doet is in je stack programma-code 'writen' en vervolgens je return-address (EIP) veranderen in de offset van deze code. Functie eindigt, foute EIP wordt opgehaald en hoppa, je process springt naar het programma dat in je stack-space staat. Dit werkt dus puur en alleen omdat je stack, data en code elkaar overlappen. Wat die patch waarschijnlijk doet is dus zorgen dat die stack buiten je code-limit valt. Dan zijn alle stack-eggdrops dus in een klap ongedaan gemaakt..

Yo dawg, I heard you like posts so I posted below your post so you can post again.


  • JayTaph
  • Registratie: Oktober 1999
  • Laatst online: 28-11-2025

JayTaph

Portability is for canoes.

Topicstarter
>Waarom beperk je je tot pchar?
>__PROTECTED__ char tmp[10];
>__PROTECTED__ int score[1000];
>int __PROTECTED__ getsome(void);

Jah. Ik zie ook geen reden waarom dat niet zou werken, maar het ging mij er alleen om dat inlezen van vars meestal gedaan word in chars (in een andere type wordt er wel een strikte check op gedaan).

>Dit werkt alleen voor talen waar de
>variable scoping simpel in elkaar zit.
>Geneste functies (zoals in bv. pascal) zijn
>niet meer mogelijk, want de inner-functions
>hebben geen toegang meer tot variabelen in
>de outer-function (die liggen immers op een
>andere stack).
Beetje het verhaal van Onno: dat zal niet zo veel uitmaken want elke geneste functie krijgt een nieuwe stack die eigenlijk ligt bovenop de oude stack. Er wordt eigenlijk steeds een stukje van de oude stack afgesnoept waarop het return address bewaard wordt zodat deze niet door de nieuwe functie kan worden gewijzigd.


> Ik denk dat dit vooral een
>debugging/testing feature gaat worden, de
>performance loss lijkt me nog al groot tov.
>het normale "flat-memory" model van c;
>vooral ook omdat je de selectors van je
>protected types naar elke functie moet
>passen die die types gebruiken. Je hebt dus
>niet alleen overhead van de selector
>creatie, maar ook nog eens die van
>(tragere) selector based instructies in
>andere functies (en grotere stacks
>uiteraard).
Ja inderdaad. Er zit een (grote) penalty op het gebruik hiervan. Maar aan de andere kant denk ik dat het best wel mee valt met de snelheidsverlies door middel van optimalisatie. Zeker voor critial systems zoals bijvoorbeeld een imapd, tcpd of popd dan heeft security een hogere prioriteit dan snelheid. Aan de andere kant, het heeft ook zeer weinig nut om je quake3-renderers te gaan ombouwen naar deze protected functies.
Ik denk dat het uiteindelijk een keuze is die je zelf als programmeur moet maken maar as said: misschien is dit hele idee morgen wel weer overboord door iets anders en ik verwacht ook niet dat de nieuwe NT kernel, linux 2.5 en 2.6 gaan draaien op dit systeem. Ik ben allang blij als ik het draaiend krijg. Als er andere mensen hiermee ook geholpen zijn dan is dat alleen mar een pre :)

<typo>
sector != selector
</typo>

Yo dawg, I heard you like posts so I posted below your post so you can post again.


  • JayTaph
  • Registratie: Oktober 1999
  • Laatst online: 28-11-2025

JayTaph

Portability is for canoes.

Topicstarter
quote:
--------------------------------------------------------------------------------
Op vrijdag 01 december 2000 20:45 schreef Onno het volgende:
Neuh, dat lijkt me geen probleem. Procedures kunnen immers gewoon andere segmenten aanspreken. Moet je alleen met far pointers gaan werken. Naja... so be it.
--------------------------------------------------------------------------------

Hmm, klopt wel. Het bijhouden van de stack chains wordt wel een stuk lastiger.
Met far pointers werken moeten we sowieso (en overal). Anders kunnen we nooit meer een pchar, of om het even welke variabele die in een protected function gedeclareerd is, passen naar een andere function. (Dit bedoelde ik te zeggen met dat tweede punt uit m'n vorige posting, maar het kwam er wat ongelukkig uit.)
Ok, hier snap ik dus geen zak van :?.. Of misschien snap ik het wel, maar krijg ik niet helemaal het plaatje door wat jullie wel krijgen. Kan je het misschien iets duidelijker uitleggen wat je ermee bedoeld? :)

Yo dawg, I heard you like posts so I posted below your post so you can post again.


Verwijderd

Als je ook andere datatypes __PROTECTED__ maakt, kun je dit gebruiken voor thread safety.

Bekijk deze funtions eens:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void A (void) {
  char tmp[10];
  gets(tmp);
  ...
}
void B (void) {
  __PROTECTED__ char tmp[10];
  gets(tmp);
  ...
}
void __PROTECTED__ C (void)
  char tmp[10];
  gets(tmp);
  ...
}

Bij function A maakt de compiler een gewone pointer naar tmp[]. Bij function B en C moet de compiler een far pointer maken. gets() zal dus met far pointers overweg moeten kunnen (en alle andere functions ook).

Met andere woorden: alle normale pointer operaties in c moeten vervangen worden door far pointer operaties.

  • JayTaph
  • Registratie: Oktober 1999
  • Laatst online: 28-11-2025

JayTaph

Portability is for canoes.

Topicstarter
Ok.. nou krijg ik het plaatje door :)

Je moet inderdaad voor elke parameter een extra descriptor meepushen. Helaas is dat niet een van de snelste instructies van de intel(mem16/32 4 of 5 clocks, pop zelfs 1tje meer), maar het pushen van de parameters (selector + memory reference of constante) worden gepaired door de CPU dus de snelheids verlies is daarom al aanzienelijk verkleint. En daarbij geld dat in een normale functie niet zo heel erg veel params mee worden gegeven (zeker geen honderden).

Far pointers lijken me dus niet zo'n heel erg groot probleem eerlijk gezegd.

Yo dawg, I heard you like posts so I posted below your post so you can post again.


Verwijderd

Ja als je de nou alleen CS executable maakt en de selectors zo aanpast dat CS, DS, en SS alleen in lineair en niet is physical memory overlappen dan heb je alleen een snelheidsverlies bij het laden van het programma door de REP MOVSD (of het kopieren via de FPU dat stukken sneller is, check www.intelligentfirm.com, maar dat ff terzijde).

Je kan er natuurlijk ook voor zorgen dat de gebruiker kan instellen of het programma 'security critical' is en aan de hand daarvan met de selectors smurfen.

Moet je natuurlijk geen ring 0 backdoortje inbouwen zoals in Win 9x...

ps UnrealMode is trouwens veel beter dan protected mode

  • JayTaph
  • Registratie: Oktober 1999
  • Laatst online: 28-11-2025

JayTaph

Portability is for canoes.

Topicstarter
>ps UnrealMode is trouwens veel beter dan
>protected mode

Ow... leer ik weer wat nieuws :P
Dit kan wel weer een leuke flamewar worden, maar unreal is niks anders als je segment limits verneuken in realmode.. en waarom realmode nog steeds niet wettelijk is verboden is mij ook een raadsel.

Yo dawg, I heard you like posts so I posted below your post so you can post again.


  • Onno
  • Registratie: Juni 1999
  • Niet online
De input-functies zijn meestal wel duidelijk aanwezig dus ik denk niet dat het een heel erg groot probleem zou zijn om uit te vinden welke functies en strings protected zouden moeten zijn
Hmm nee, misschien ook wel niet...
Maar zeker niet minder interessant. Wat eigenlijk elk OS nodig heeft, is een lowlevel functie-set die elke programma tot zijn beschikking heeft. Of dit nu een API is die meegelinkt word, of dit via een software-int gedaan wordt (Linux 0x80, MS-DOS 0x21, lijkt mij niet zoveel uit te maken). Waar zit dan die 'inflexibiliteit'?
Nou, stel je bijvoorbeeld eens voor dat je ooit de support voor een bepaalde functie wilt droppen. (ook OS'en evolueren tenslotte, en om compatible te blijven met alles is niet altijd even goed voor je OS)
Als je dan software interrupts gebruikt zul je bij een programma die die oude functie gebruikt tijdens runtime een fout krijgen. Of nog erger, een onvoorspelbaar resultaat.
Als je alle functies op naam importeert, heb je dat niet. Dan gaat het al fout tijdens het laden van het programma, en kan je OS een nette foutmelding geven.
(natuurlijk weerhoudt niets je ervan om sommige systeem functies te implementen mbv software ints, die zijn nl. gewoon handig voor calls naar een andere ring, maar in programma's gebruik ik ze niet)

Ik vind named imports gewoon in alle gevallen mooier. Ook voor die paar low level system calls. :)

  • JayTaph
  • Registratie: Oktober 1999
  • Laatst online: 28-11-2025

JayTaph

Portability is for canoes.

Topicstarter
>Ik vind named imports gewoon in alle
>gevallen mooier. Ook voor die paar low
>level system calls.

Da's dan maar goed dat je dit aan het einde nog meldt :), want daar praat je dan namelijk over. Die interrupt calls gebruik op het laagste niveau. Daarboven ligt gewoon een wrap-functie met een mooi naampje en dus zijn de interrupts alleen voor interne kernel-development van belang.. Maar ja.. misschien om consistent te blijven met de rest van development dat het interdaad wel makkelijker is.. (beter wil ik niet zeggen en als we gaan pielen op kloktikken dan al helemaaaal niet sneller :))

Yo dawg, I heard you like posts so I posted below your post so you can post again.


  • JayTaph
  • Registratie: Oktober 1999
  • Laatst online: 28-11-2025

JayTaph

Portability is for canoes.

Topicstarter
Misschien wel leuke theorieen om in TweakOS te verwerken misschien? ;)

Yo dawg, I heard you like posts so I posted below your post so you can post again.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Op vrijdag 01 december 2000 16:00 schreef JayTaph het volgende:
Een tijdje geleden was er een
interessant topic over buffer overflows en exploits.
1. Why is the base and limit of the code the same as the base of the data?
2. Why is the stack in the same descriptor as the data?
There should be no need for the stack and data to be the same memory area, also, there should be no need for the code to be in the same area as the data or stack.
Maar welke extra veiligheid bereik je door data en stack te scheiden?
Limitations of the LDT
----------------------
A local descriptor table can only store 8192 selectors. With the exception of 3 selectors (text, data, stack) this leaves 8189 selectors for Pchars and PFunction. This should be more than enough for a single program, but even when it's not, there is always the possibility to switch to another LDT. Off course, even the GDT supports 8192 descriptors (including TSS, including global descriptors) so Pchars entries will eventually exhaust. I think the changes on that are very slim.
Als je toevallig een recursieve functie hebt die pchars gebruikt en die diep genoeg gaat heb je een groot probleem.

  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Op vrijdag 01 december 2000 21:07 schreef mietje het volgende:

[..]

Hmm, klopt wel. Het bijhouden van de stack chains wordt wel een stuk lastiger.

Met far pointers werken moeten we sowieso (en overal). Anders kunnen we nooit meer een pchar, of om het even welke variabele die in een protected function gedeclareerd is, passen naar een andere function. (Dit bedoelde ik te zeggen met dat tweede punt uit m'n vorige posting, maar het kwam er wat ongelukkig uit.)
Dual stacks worden volgens mij al toegepast hoor. En volgens mij niet met far pointers. Maar er wordt gewoon een gedeelte van de stack gekopieerd.

  • wasigh
  • Registratie: Januari 2001
  • Niet online

wasigh

wasigh.blogspot.com

topic is te oud, start eventueel een nieuwe
Pagina: 1

Dit topic is gesloten.