Please refer the F&Q post on the piazza.
rand.h
file given in the spec
Original: #define XV6_RAND_MAX = 2147483647
Updated: #define XV6_RAND_MAX 2147483647
partner.login
and SLIP_DAYS
file to /<cs-login>/p3a/
directoryFor this project, you have the option to work with a partner. Read more details in Submitting Your Implementation and Collaboration.
In the first part of project, you’ll be adding read only access to some pages in xv6, to protect the process from accidentally modifying it.
You will be implementing 2 system calls:
int mprotect(void *addr, int len)
to add read-only protection to pages.int munprotect(void *addr, int len)
to give read-write permission to pages back.In the second part, you will implement two basic page allocation schemes. The first scheme allocates frames alternatively to
prevent malicious writes. The second scheme allocates frames randomly. You also need to implement a random number generator for second scheme.
You will implement a system call int dump_allocated(int *frames, int numframes)
which dumps information about the allocated frames.
In most operating systems, code is marked read-only instead of read-write. However, in xv6 this is not the case, so a buggy program could accidentally overwrite its own text. Try it and see!
In this portion of the xv6 project, you’ll change the protection bits of parts of the page table to be read-only, thus preventing such over-writes, and also be able to change them back.
To do this, you’ll be adding two system calls: int mprotect(void *addr, int len)
and int munprotect(void *addr, int len)
.
Calling mprotect()
should change the protection bits of the page range starting at addr
and of len
pages to be read only. Thus, the program could
still read the pages in this range after mprotect()
finishes, but a write to this region should cause a trap (and thus kill the process). The munprotect()
call does the opposite: sets the region back to both readable and writeable.
Also required: the page protections should be inherited on fork(). Thus, if a process has mprotected some of its pages, when the process calls fork, the OS should copy those protections to the child process.
There are some failure cases to consider: if addr
is not page aligned, or addr
points to a region that is not currently a part of the address
space, or len
is less than or equal to zero, return -1 and do not change anything. Otherwise, return 0 upon success.
Hint: after changing a page-table entry, you need to make sure the hardware knows of the change. On 32-bit x86, this is readily accomplished by updating
the CR3
register (what we generically call the page-table base register in class). When the hardware sees that you overwrote CR3
(even with the same
value), it guarantees that your PTE updates will be used upon subsequent accesses. The lcr3()
function will help you in this pursuit.
As you know, the abstraction of an address space per process is a great way to provide isolation (i.e., protection) across processes. However, even with this abstraction, attackers can still try to modify or access portions of memory that they do not have permission for.
For example, in a Rowhammer attack, a malicious process can repeatedly write to certain addresses in order to cause bit flips in DRAM in nearby (physical) addresses that the process does not have permission to write directly. In this part, you’ll implement one (not very sophisticated) ways to alleviate the impact of a malicious process: allocating pages from different processes so that they are not physically adjacent.
One of the advantages of page-based systems is that a virtual page can be allocated in any free physical page (or frame); all physical pages are supposed to be equivalent. However, not paying attention to frame allocation could allow a malicious process to repeatedly write to addresses at the edge of one of its pages, which due to the low-level properties of modern DRAM, could then modify the values at the edge of the next physical page which happens to belong to a different process.
kalloc.c
. By looking at kinit()
you can see how the free list is initially
created to include all physical pages across a range and you can see that kalloc() simply returns the first page on that free
list when a frame is needed. There are a different levels of sophistication for page allocation to reduce the physical memory wastage. For this project however, we ask you to write two
very basic implementations. In both these implementations, you will be modifying the kalloc()
method.
In this implementation, you keep a free frame between every allocated frame in the system. For example, if kalloc() sees 3 frame allocations for process a, then 2 frame allocations for process b, and then 2 for process a again, it will end up allocating the pages of physical memory to processes as follows (F represents a free page): aFaFaFbFbFaFa. This approach satisfies our security requirements, but uses twice as much memory as the original xv6 approach.
The changes for alternate allocation as well as Part 1 should be submitted in p3a subdirectory. We will run test cases for these two parts in your p3a subdirectory.
In this implementation, the allocator assigns random entries in the free list. For this purpose, you will need to implement a
pseudo random number generator, which gives a random number each time. We have provided a header file rand.h
below.
#ifndef _RAND_H
#define _RAND_H
#define XV6_RAND_MAX 2147483647
/*
Return a random integer between 0 and XV6_RAND_MAX inclusive.
NOTE: If xv6_rand is called before any calls to xv6_srand have been made, the same
sequence shall be generated as when xv6_srand is first called with a seed value of 1.
*/
int xv6_rand (void);
/*
The xv6_srand function uses the argument as a seed for a new sequence of pseudo-random numbers to be returned by subsequent calls to rand.
If xv6_srand is then called with the same seed value, the sequence of pseudo-random numbers shall be repeated.
If xv6_rand is called before any calls to xv6_srand have been made, the same sequence shall be generated as when xv6_srand is first called with a seed value of 1.
*/
void xv6_srand (unsigned int seed);
#endif // _RAND_H
You have to implement the two functions xv6_rand()
and xv6_srand()
in rand.c
. The xv6_rand()
method generates a random number
on each call and xv6_srand()
method is used to set a seed. We recommend using the Xorshift family of
random number generators, although you are welcome to use other approaches.
Each time in your kalloc implementation, the allocator should call the xv6_rand() method
to generate a random number x
. Then, it takes its remainder with the current free list size to get y
, and returns the yth
frame
from start in the free list. For example: if the remainder is 0, the first frame in free list is allocated, when remainder is 1 second frame is
allocated .. and so on. Also, don’t forget to remove the allocated frame from the free list!
rand.h
and rand.c
in the kernel subdirectory.kinit()
method. Our test cases assume standard free list
initilisation. You can however use variables to maintain size of free list and history of allocated frames.xv6_rand()
method generates a random number based on some seed. When the seed is not specifically set using xv6_srand()
method,
it assumes seed of 1. However, when a seed is set, the next call to xv6_rand()
should give the first random number corresponding to that seed.xv6_rand()
method implementation, the following code gives output as below:xv6_srand(2);
printf(" %d\n", xv6_rand()); //outputs 13438
xv6_srand(5);
printf(" %d\n", xv6_rand()); //outputs 64829
Then for the following code also, output should be independent of previous seed.
xv6_rand();
xv6_srand(5);
printf(" %d\n", xv6_rand()); //should output 64829
The changes for random allocation should be submitted in p3b subdirectory. We will run test cases for random allocation on submissions in p3b subdirectory.
You also have to implement a system call to dump the pages which have been allocated. This will help you debug your kernel and us to test your code.
int dump_allocated(int *frames, int numframes)
(23, 19, 16, 15, 13, 8, 5, 3)
. It then allocates four frames with address 8, 19, 5, 15
in order.
The freelist should now become (23, 16, 13, 3)
. And a call to dump_allocted(frames, 3)
, should have frames
pointing to array (15, 5, 19)
.mkdir -p ~cs537-1/handin/LOGIN/p3a/src
mkdir -p ~cs537-1/handin/LOGIN/p3b/src
To submit your solution, copy all of the xv6 files and directories with your changes for alternate allocation and Part 1 into ~cs537-1/handin/<cs-login>/p3a/src
,
and changes for random allocation into ~cs537-1/handin/<cs-login>/p3b/src
.
One way to do this is to navigate to your solution’s working directory and execute the following commands:
cd <your p3a working directory>
cp -r . ~cs537-1/handin/<cs-login>/p3a/src/
cd <your p3b working directory>
cp -r . ~cs537-1/handin/<cs-login>/p3b/src/
If you choose to work in pairs, only one of you needs to submit the source code. But, both of you need to submit an additional partner.login
file inside /<cs-login>/p3a/>
, which contains one line that has the CS login of your partner (and nothing else). If there is no such file, we are assuming you are working alone. If both of you turn in your code, we will just randomly choose one to grade.
Consider the following when you submit your project:
SLIP_DAYS
file in the /<cs-login>/p3a/
directory otherwise we use the submission on the due date.README
file in the main directory~cs537-1/handin/<cs-login>/p3a/src
directory. Having subdirectories in <cs-login>/p3a/src
like <cs-login>/p3a/src/xv6-sp20
or … is not acceptable.This project is to be done in groups of size one or two (not three or more). Now, within a group, you can share as much as you like. However, copying code across groups is considered cheating. If you are planning to use git or other version control systems (which are highly recommended for this project), just be careful not to put your code in a public repository.