|
| 1 | +--- |
| 2 | +title: Tock |
| 3 | +position: 0 |
| 4 | +--- |
| 5 | + |
| 6 | +# Introduction |
| 7 | + |
| 8 | +## Prerequisites |
| 9 | + |
| 10 | +:::note Ubuntu Virtual Machine |
| 11 | +This workshop will not work on Windows systems. |
| 12 | +You can use the Ubuntu VM we provide [here](https://drive.google.com/file/d/1WSUo29d9Z8bmcjurvkDUmoAgq1TqaW4H/view?usp=sharing) (only works on VirtualBox). |
| 13 | +The username and password are both `ipwembedded`. |
| 14 | +The VM has the port 3033 forwarded for SSH connection. |
| 15 | +::: |
| 16 | + |
| 17 | +If you did not attend the **Tock Workshop**, please follow the [Setup Tutorial](../../tock_workshop/index.md). |
| 18 | + |
| 19 | +## Getting Started |
| 20 | + |
| 21 | +For this track we will be using the **Nucleo-F429ZI** boards. You will need to **change the branch** you are working on, but first make sure you commit your changes. |
| 22 | + |
| 23 | +```shell |
| 24 | +git add . |
| 25 | +git commit -m "tock workshop progress" |
| 26 | +``` |
| 27 | + |
| 28 | +Then, to fetch the branch and work on it, run: |
| 29 | + |
| 30 | +```shell |
| 31 | +git fetch |
| 32 | +git checkout track/embedded |
| 33 | +``` |
| 34 | + |
| 35 | +The board's main can be found in the `boards/nucleo_f429zi` subfolder. Try to flash the kernel to the board, using the board's `Makefile`. After you are done flashing, connect to the board using `tockloader listen` |
| 36 | + |
| 37 | +```shell |
| 38 | +[INFO ] No device name specified. Using default name "tock". |
| 39 | +[INFO ] No serial port with device name "tock" found. |
| 40 | +[INFO ] Found 2 serial ports. |
| 41 | +Multiple serial port options found. Which would you like to use? |
| 42 | +[0] /dev/cu.debug-console - n/a |
| 43 | +[1] /dev/cu.usbmodem1303 - STM32 STLink |
| 44 | + |
| 45 | +Which option? [0] 1 |
| 46 | +[INFO ] Using "/dev/cu.usbmodem1303 - STM32 STLink". |
| 47 | +[INFO ] Listening for serial output. |
| 48 | + |
| 49 | +tock$ |
| 50 | +``` |
| 51 | + |
| 52 | +## Customize your kernel |
| 53 | + |
| 54 | +After connecting to Tock's terminal, you can run `help` to see the supported commands. One of them is `reset` and by running it, you can see the default *"welcome"* message. |
| 55 | + |
| 56 | +```shell |
| 57 | +tock$ reset |
| 58 | +Initialization complete. Entering main loop |
| 59 | +tock$ |
| 60 | +``` |
| 61 | + |
| 62 | +Personalize your kernel, by changing the hostname and the welcome message. |
| 63 | + |
| 64 | +## Print Counter Capsule |
| 65 | + |
| 66 | +For this task, you will need to build a capsule that prints a custom message each time it receives a print command from an application, along with a message counter representing the number of commands received. Remember that you will need to implement the `SyscallDriver` trait. |
| 67 | + |
| 68 | +### The simple way |
| 69 | + |
| 70 | +Simplest method to do this is to add a `counter` field in the capsule's structure. One issue you will most likely encounter is that the `command` method required by the `SyscallDriver` trait has a immutable reference to `&self`, so you may need to wrap the counter in a wrapper that allows for inner mutability, such as `Cell`. |
| 71 | + |
| 72 | +### The Tock way |
| 73 | + |
| 74 | +One issue with the previous approach is that the counter would be shared between the applications. This could be an issue for mutually distrustful application. Fortunately, Tock has a mechanism in place for such situations, called `Grant`s, which are per-process memory regions allocated by the kernel in a process memory region for a capsule to store that process’s state. |
| 75 | + |
| 76 | + |
| 77 | + |
| 78 | +To access this region, you can simply add a new `grant` field in the capsule structure. |
| 79 | + |
| 80 | +```rust title="capsules/extra/src/print_counter.rs" |
| 81 | +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; |
| 82 | + |
| 83 | +// TODO: Define `App` structure. Make sure to satisfy trait constraints. |
| 84 | +struct App; |
| 85 | + |
| 86 | +struct PrintCounter { |
| 87 | + grant: Grant< |
| 88 | + App, |
| 89 | + UpcallCount<0>, // Number of upcalls supported by the capsule |
| 90 | + AllowRoCount<0>, // Number of Read-Only buffers supported |
| 91 | + AllowRwCount<0>, // Number of Read-Write buffers supported |
| 92 | + >, |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +As before, we will need to define a component for this capsule, to initialize it. |
| 97 | + |
| 98 | +```rust title="boards/components/src/print_counter.rs" |
| 99 | +#[macro_export] |
| 100 | +macro_rules! print_counter_component_static { |
| 101 | + ($(,)?) => {{ |
| 102 | + kernel::static_buf!(capsules_extra::print_counter::PrintCounter) |
| 103 | + };}; |
| 104 | +} |
| 105 | + |
| 106 | +pub struct PrintCounterComponent; |
| 107 | + |
| 108 | +impl Component for PrintCounterComponent { |
| 109 | + type StaticInput = &'static mut MaybeUninit<capsules_extra::print_counter::PrintCounter>; |
| 110 | + |
| 111 | + type Output = &'static capsules_extra::print_counter::PrintCounter; |
| 112 | + |
| 113 | + fn finalize(self, static_memory: Self::StaticInput) -> Self::Output { |
| 114 | + todo!() |
| 115 | + } |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +Grants are a sensitive component of the operating system, so the creation and management operations are considered unsafe, and require |
| 120 | +privileges to perform. Tock restricts these privileged operations through the use of capabilities, which are tokens implementing `unsafe` traits. Because capsules are forbidden from using unsafe code, these tokens cannot be forged. |
| 121 | + |
| 122 | +Creating a grant is requires a reference to the board's kernel, and a driver number, so we will need to add these parts in the components. |
| 123 | + |
| 124 | +```rust title="boards/components/src/print_counter.rs" |
| 125 | +pub struct PrintCounterComponent { |
| 126 | + driver_num: usize, |
| 127 | + board_kernel: &'static kernel::Kernel, |
| 128 | +} |
| 129 | + |
| 130 | +impl PrintCounterComponent { |
| 131 | + pub fn new(driver_num: usize, board_kernel: &'static kernel::Kernel) -> Self { |
| 132 | + Self { |
| 133 | + driver_num, |
| 134 | + board_kernel, |
| 135 | + } |
| 136 | + } |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +The capability needed for grant creating is called `MemoryAllocationCapability`, and it can be found in the `kernel::capabilities` module. The `kernel` also exposes the `crate_capability!` macro for ease of use. |
| 141 | + |
| 142 | +```rust title="boards/components/src/print_counter.rs" |
| 143 | +impl Component for PrintCounterComponent { |
| 144 | + // ... |
| 145 | + |
| 146 | + fn finalize(self, static_memory: Self::StaticInput) -> Self::Output { |
| 147 | + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); |
| 148 | + let grant = self.board_kernel.create_grant(self.driver_num, &grant_cap); |
| 149 | + |
| 150 | + static_memory.write(capsules_extra::print_counter::PrintCounter::new(grant)) |
| 151 | + } |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +:::note `new` constructor |
| 156 | +You will also need to implement the `new` constructor for the `PrintCounter` capsule. |
| 157 | +::: |
| 158 | + |
| 159 | +Next, you must implement the `SyscallDriver` trait, where the command logic will be. For the `allocate_grant` method implementation, it is enough to use the `enter` method of the Grant which takes a closure with two parameters. |
| 160 | + |
| 161 | +```rust |
| 162 | +fn allocate_grant(&self, process_id: kernel::ProcessId) -> Result<(), kernel::process::Error> { |
| 163 | + self.grant.enter(process_id, |_, _| {}) |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +For the command logic, you must also use the `enter` API. The first parameter of the closure will be a mutable reference to a `GrantData` wrapper over the previously defined `App`. The wrapper is transparent, meaning it permits accessing fields of the generic type. |
| 168 | + |
| 169 | +The next step is configuring the capsule in the board's main file. Remember you need to add the capsule in the `NucleoF429ZI` structure, the `SyscallDriverLookup` and initialize the printer counter capsule. |
| 170 | + |
| 171 | +## The role of a Scheduler |
| 172 | + |
| 173 | +This task aims to illustrate the importance of OS preemption in the context of untrusted applications. Currently the scheduler used by the board is `kernel::scheduler::RoundRobinSched`, which implements a classical scheduling algorithm, allowing each process to run up to a maximum time slice called **quanta**. In the event that an application tries starving all other processes, the kernel will interrupt the malicious application after its quanta expires and will then schedule another process. |
| 174 | + |
| 175 | +### Trust but verify |
| 176 | + |
| 177 | +:::warning app flashing |
| 178 | +<!-- If you are on Windows/VM, using `tockloader install` (which the `Makefile` command uses) may not work, so you will need to bundle the application in a single binary, which you will need to load in the *"old-fashioned"* way, by modifying the `APP` variable in `boards/nucleo_f429zi/Makefile` to point to the bundle TBF. |
| 179 | +
|
| 180 | +To create the bundle, you will need to concatenate the two `.tbf`s using `cat`: |
| 181 | +
|
| 182 | +```shell |
| 183 | +cat app1.tbf app2.tbf > bundle.tbf |
| 184 | +``` |
| 185 | +
|
| 186 | +Then, modify the `Makefile`: |
| 187 | +
|
| 188 | +```makefile title="boards/nucleo_f429zi/Makefile" |
| 189 | +APP=<path/to/bundle.tbf> |
| 190 | +``` |
| 191 | +
|
| 192 | +Lastly, flash it by running `make program`. --> |
| 193 | + |
| 194 | +You will need to install `openocd` first. |
| 195 | + |
| 196 | +```shell |
| 197 | +# Linux |
| 198 | +sudo apt install openocd |
| 199 | + |
| 200 | +# Mac |
| 201 | +brew install openocd |
| 202 | +``` |
| 203 | + |
| 204 | +::: |
| 205 | + |
| 206 | +Your task will be to verify the previous claims, by flashing two C applications. One of them will be the `blink` example. After flashing the kernel by running `make flash` in the board's main directory (`boards/nucleo_f429zi`), you can load the application by running `make flash` in the example's root folder (`example/blink`). |
| 207 | + |
| 208 | +As there are no *"malicious"* examples, we will have to add them on our own. In this case, an app that would print a message, then just infinitely spin in a `while` loop is enough. For this, you can adapt the `examples/c_hello` example, the flash it. |
| 209 | + |
| 210 | +If you managed to flash both applications, you should be able to connect to the board using `tockloader listen` and see a similar output when running `list`: |
| 211 | + |
| 212 | +```shell |
| 213 | +tockloader listen |
| 214 | +[INFO ] No device name specified. Using default name "tock". |
| 215 | +[INFO ] No serial port with device name "tock" found. |
| 216 | +[INFO ] Found 2 serial ports. |
| 217 | +Multiple serial port options found. Which would you like to use? |
| 218 | +[0] /dev/cu.debug-console - n/a |
| 219 | +[1] /dev/cu.usbmodem1103 - STM32 STLink |
| 220 | + |
| 221 | +Which option? [0] 1 |
| 222 | +[INFO ] Using "/dev/cu.usbmodem1103 - STM32 STLink". |
| 223 | +[INFO ] Listening for serial output. |
| 224 | + |
| 225 | +tock$ list |
| 226 | + PID ShortID Name Quanta Syscalls Restarts Grants State |
| 227 | + 0 Unique blink 0 289 0 1/11 Yielded |
| 228 | + 1 Unique ws-demo 640 6 0 1/11 Running |
| 229 | +tock$ |
| 230 | +``` |
| 231 | + |
| 232 | +You should be able to see the on-board LEDs flashing on the board. |
| 233 | + |
| 234 | +### Cooperation flaw |
| 235 | + |
| 236 | +Now, let's test the same scenario, but with a cooperative scheduling mechanism. You have to first change the kernel's scheduler in the board's `main.rs` file to use the `scheduler::cooperative::CooperativeSched`. Then you must re-flash the kernel by running `make flash`. Fortunately flashing the kernel should preserve the applications, so you will not have to re-flash them as well. |
| 237 | + |
| 238 | +After you are done flashing, check that both applications are present. You can try to reset the board a few times by running `reset` in tock's process console (the terminal you open by running `tockloader listen`). |
0 commit comments