From fa65b003fd4aa473089e6a9e6abfd963709df847 Mon Sep 17 00:00:00 2001 From: AVDestroyer Date: Wed, 18 Sep 2024 19:16:47 -0700 Subject: [PATCH] os lab part 2 blog post --- data/blog/2023-08-26-first-blog-post.md | 59 ------------------ .../2023-12-04-fall-2023-os-part-2-lab.md | 62 +++++++++++++++++++ 2 files changed, 62 insertions(+), 59 deletions(-) delete mode 100644 data/blog/2023-08-26-first-blog-post.md create mode 100644 data/blog/2023-12-04-fall-2023-os-part-2-lab.md diff --git a/data/blog/2023-08-26-first-blog-post.md b/data/blog/2023-08-26-first-blog-post.md deleted file mode 100644 index a7b008a..0000000 --- a/data/blog/2023-08-26-first-blog-post.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: First Blog Post -authors: [Cyber McCybean] -category: Projects -tags: [example] -description: Our first blog post on the new website! ---- - -blog image - -In today's digital age, cybersecurity is more important than ever. With so much sensitive information being stored and transmitted online, it is essential for businesses to implement strong security measures to protect themselves and their customers. Unfortunately, even the most secure systems can sometimes be breached, as was the case with a recent cybersecurity incident that affected our company. - -## The Incident - -On the morning of May 1st, our IT team discovered that an unauthorized individual had gained access to our company's network. The attacker had used a phishing email to trick one of our employees into clicking on a malicious link, which gave the attacker access to their login credentials. With this information, the attacker was able to gain access to our network and steal sensitive information. - -Our team immediately took action to contain the incident and prevent further damage. We isolated the affected systems and conducted a thorough investigation to determine the extent of the breach. Unfortunately, we found that the attacker had been able to access and steal confidential data, including financial information and personal data of our customers. - -## Response and Recovery - -We promptly informed our customers about the incident and the steps we were taking to mitigate the impact. We offered credit monitoring and identity theft protection services to affected customers, and we worked closely with law enforcement and cybersecurity experts to identify the attacker and prevent future attacks. - -We also reviewed and updated our security measures to prevent similar incidents in the future. We implemented stricter access controls, improved our email filtering system, and provided additional security training to our employees. We also performed regular security audits and vulnerability assessments to ensure that our systems were up-to-date and secure. - -## Lessons Learned - -This cybersecurity incident was a wake-up call for our company. It highlighted the importance of having strong security measures in place and the need for ongoing security training and awareness. We learned that even a single click on a malicious link can have serious consequences, and that we need to be vigilant in our efforts to protect our systems and our customers' information. - -We also learned the importance of having a response plan in place for dealing with cybersecurity incidents. Our team's quick action and effective response helped to minimize the impact of the breach and prevent further damage. - -```python -# poggies -import os -osssssssssss.system('ls') - -int pog = 5 -for i in range(10): - print(i) -``` - -## Conclusion - -Cybersecurity incidents can happen to any company, no matter how secure their systems may seem. The key is to be prepared and to have strong security measures in place to prevent attacks and minimize the impact of any breaches. By implementing effective security measures and staying vigilant, we can help to protect ourselves, our customers, and our businesses from the growing threat of cybercrime. - -## Sample Data - -```python -# poggies -import os -os.system('ls') - -int pog = 5 -for i in range(10): - print(i) -``` - -> Hello This is a block quote - -![image test](https://reciprocity.com/wp-content/uploads/2021/08/blog_what-is-cybersecurity-framework_featured-img_730x270.jpg) diff --git a/data/blog/2023-12-04-fall-2023-os-part-2-lab.md b/data/blog/2023-12-04-fall-2023-os-part-2-lab.md new file mode 100644 index 0000000..602db97 --- /dev/null +++ b/data/blog/2023-12-04-fall-2023-os-part-2-lab.md @@ -0,0 +1,62 @@ +--- +title: Secure OS Lab Part 2 +authors: [Jason An] +category: Projects +tags: [fall-2023, cyber-lab] +description: Creating a secure OS +--- + +# Cyber OS Blogpost +## Overview +We started this quarter with basically just a bootloader, terminal driver, basic libc library, and an empty kernel. From this starting point, we had to start building out all of the components of the kernel that allow an operating system to properly function. Specific details are in the sections below. + +## Virtual Memory/Paging +In x86 and most CPU architectures, there's a distinction between physical and virtual memory. Physical memory addresses directly and linearly into RAM, and has access to the entire memory space. When the OS first boots, this is the default addressing mode. This is unideal for many reasons. Every process would be given access to the entire memory space, which lets them read and modify the memory of other processes, or worse, the kernel. It's also hard to partition memory properly between processes, as each process would somehow have to know what regions of memory are safe to write into without any collisions. + +This is the purpose of virtual memory: by utilizing a technique called "paging", every process can be given its own virtual memory space, which non-linearly maps onto physical memory in a manner that can be controlled by the kernel, along with additional protections, like non-executable memory or kernel-only memory. + +A page of memory in x86 is 4096 (0x1000) aligned bytes, and is the minimum unit of memory for address translation. A page table describes how these pages in virtual memory map to pages in physical memory, if they even map anywhere at all. + +Concretely, how paging works in x86 is that the kernel writes a top-level page table to physical memory then moves its physical address into the cr3 control register, which enables the memory management unit (MMU) that manages paging. Another layer of optimization is the translation lookaside buffer (TLB) which caches page table entries, which means that the TLB has to be flushed every time a page table entry is changed. The reason the page table is referred to as "top-level" is because x86 actually has hierarchical paging with multiple levels of page tables. In 32-bit x86, there are 2 levels, and in 64-bit x86, there are 4 levels with an optional 5th with CPU extensions. This is because a flat page table containing everything would take up too much memory by itself, so each level of page table only encodes a portion of the bits of the virtual address. + +We wrote a simple freelist page frame allocator exposed as a `palloc` function, which is responsible for taking in a virtual address, then finding a free page in physical memory and creating the respective entry in the page table to map the virtual address there. There's also a `pfree` function that takes in a virtual address and deletes the page table entry for it. This is where the freelist comes in: in order to reuse freed physical pages, a singly linked list of freed pages is maintained in the freed pages themselves, so it uses no additional space. To free a page, the freelist head is stored in the page, then the freelist head is set to the freed page. To allocate a page, if the freelist head is not NULL, then the freelist head will be the allocated page, and the head will be advanced to the next element in the list. If the freelist head is NULL, that means there are no reusable pages, so a new page is chosen at the end of the allocated pages. + +## Heap Allocator +The heap is a pool of memory conceptually divided into fixed-size regions called chunks, providing a method for dynamic allocation. The OS can request arbitrarily sized chunks for use at run-time through the `kalloc` function and free these chunks for reuse through the `kfree` function. The heap allocator is responsible for appropriately reusing and consolidating freed chunks to minimize fragmentation. + +Our implementation stores metadata including the size and in-use status of chunks immediately before the chunk data in memory. Freed chunks are stored in a doubly linked-list and repurpose the chunk data to store free-list pointers. Upon freeing a chunk, the in-use status of immediately adjacent chunks in memory (forwards and backwards) are checked to perform consolidation such that adjacent free chunks are merged into a single larger free chunk. An iteration through the free-list upon subsequent allocations utilizes a first-fit remaining strategy for speed. + +The heap itself is implemented as a contiguous region of memory. The wilderness chunk represents the last page of memory that has been allocated for the heap using the kernel's `palloc` function, and can be dynamically grown to satisfy allocation requests that are not satisfied by the free-list. + +An improvement to our heap allocator could utilize fixed-size bins as a caching mechanism for freed chunks, serving as both a space and time efficiency improvement, as a best-fit strategy would be implicitly utilized in constant time. + +## Interrupts + IDT +Interrupts allow for processes and hardware devices to alert the kernel of various events occurring. For example, every time you're in your browser and you move your mouse, your operating system needs to be alerted that signals are being sent from your mouse. After the operating system is alerted, the input is then received and handled, usually by updating the position of the cursor and changing the location it is displayed on the screen. + +In order to enable this functionality, we first had to create an Interrupt Descriptor Table, or IDT, which is essentially a list of pointers that tell the OS where to jump to upon receiving an interrupt signal. These "pointers" are not actually pointers denoted by `*variable_name` like you would see in C source code but a specially formatted struct which we call `idt_entry_t` which contain the address of the function to jump to as well as segment selectors for kernel code in our Global Descriptor Table. In addition to creating the structs for the IDT, the functions that the IDT point to also need some modifications from the default compiled GCC functions. Our Interrupt Service Routines, which are the first 32 functions loaded into our IDT, are reserved by the Intel x86 CPU architecture to allow for the OS kernel to be informed of certain CPU events like page faults, segmentation faults, divide by 0 errors, etc. Additionally, 16 Interrupt Requests, which are raised by hardware devices like the keyboard and timer, also require special function procedures to properly initialize the stack frames. For this, we created function stubs in ASM and used `extern` to access them within our C source code. + +After creating these function stubs and linking them to our C kernel code, we also needed to created custom structs to access the parameter data getting pushed to the stack frame by our previously defined interrupt routine stubs, which we named `registers_t` and contains all the values of the registers pushed by the interrupt routine stubs as well as the interrupt numbers and error codes. + +Then, after all of this setup was done, we enabled the PIC, which is a piece of hardware that controls our hardware interrupts, to remap the PIC pins to interrupt numbers and initialize some additional settings on the PIC. To tell the CPU the location of the IDT, we used the `lidt` instruction with the address of the IDT and then ran the `sti` instruction to enable interrupts. + +## Keyboard Driver +In order to receive text input from the user, the OS needs some kind of way to interpret electrical signals from the keyboard into valid ASCII. To do this, we need a keyboard driver to translate keyboard events like `Q_PRESSED` and `Q_RELEASED` into the correct characters that we want the kernel to receive. + +After setting up the IDT, creating a keyboard driver is quite trivial. All we need is a function in the correct format, specifically a function of type `typedef void (*isr_t)(registers_t *);`, which just means that we have a function that returns `void` and accepts a single parameter of type `registers_t*` and we define a type called `isr_t` that is a function pointer that follows these constraints. + +To actually get the signal from the keyboard, we have to read the CPU pin/port labeled 0x60, which is accomplished by `uint8_t scan_code = inb(0x60);`. Then, to handle the input from the keyboard, all we have to do is map the scan codes from the keyboard to ASCII characters and we're done with our driver! What this looks like in C code is basically just a long switch-case statement for and printing the ASCII character on the screen. + +## Serial Driver +Given our bare bones infrastructure, debugging is quite a challenge. + +Serial is a method of transmitting data one bit at a time. + +While writing a serial driver might seem a less than critical task, this is not the case for one particular reason: debugging. At the beginning of the quarter, debugging was a serious hassle. Simple print statement debugging generally requires some sort of terminal. However, our entire application is a single terminal and it's common for it to break completely when there is a bug. Additionally, the data we can display is limited to the size of the terminal, and the limited set of ASCII characters we support. + +To make debugging significantly easier we can instead use serial to write any number of arbitrary bytes to some application outside the operating system (eg. terminal, file, gdb, etc.). + +We used this to create unit tests that write error messages and IDs or to print the contents of each registry with the intent to create an output pipeline to the serial driver. + +With this, we were able to create a workaround for our debugging process by directly writing data to the serial port, rather than to the emulation terminal. This resulted in making our quality of life easier while continuing our development of the kernel. + +Additionally, we also took a look at implementing the ability to read from serial, which would allow us to read bit data from the serial port stream. Due to time limit constraints, we did not implement this entirely, and we made the decision to focus on other critical aspects of the project instead.