2025-06-26: Exam Embedded Computing 1#

Below you find descriptions of “situations”, like the code of a program that you are supposed to analyse, together with an explanation.

After each such situation description you see a table of “statements”, where each row has four fields.

  • Statement: a statement about the situation. The statement can either be true or false.

  • True: check that field with an ‘x’ if you think the statement is true.

  • False: check that field with an ‘x’ if you think the statement is false.

  • Why: This field is optional. If you are unsure about your answer, write a few words explaining why you think your answer is correct. If your reasoning is not complete nonsense, /me might diverge a little from his pure boolean “correct/incorrect” grading decision.

Not applying a ‘x’ to any of the “True” and “False” fields in a single row will render your answer incorrect, obviously, as does checking both fields.

No computers, no cellphones, no AI glasses, no earpods, no internet! Only pen and paper.

Grading algorithm

Inside one situation,

  • Each row counts as 1

  • The statement’s outcome is calculated as

    • The sum of correct rows

    • Minus the sum of incorrect rows

Globally,

  • Each situation has a weight attached (situations, together with their statements, are not equal in their difficulty)

  • The sum of the weighted situation outcomes is then scaled up to 40% (the theoretical part) as per syllabus

Stray Pointers#

Say you compile and run the program below, which is obviously buggy. Consider how large the resulting suffering can become.

int main()
{
    int* address = (int*)666;
    *address = 7;
    return 0;
}

Statement

True

False

Why

The incident is logged, you are logged out of your computer immediately, and you may never log in again.

.

.

The program is compiled and run on the Raspberry Pi. The designers of that hardware platform show some kind of humor that is prevalent among the British: the GPIO hardware inside the Pi SoC has its register map starting at address 666. Depending on the value that is written to that base address, you might end up shorting 3V3 to ground on pin 25.

.

.

The OS intercepts a hardware exception from the Memory Management Unit (MMU) which says, “At 666 there is no memory allocated for the current process”. As a result the OS terminates the process.

.

.

A fellow process on the system might have rightfully allocated memory at position 666, and it is that process that falls victim to your mistake.

.

.

CPU Time#

Compared to the program from Stray Pointers, the program below is not exactly buggy in that sense, but misbehaves in a certain way.

int main()
{
    while (true) {}
    return 0;
}

Statement

True

False

Why

On a single-processor system, a process running that program’s code will never release the processor, and no other process will ever have a chance to run again.

.

.

On a single-processor system, the OS kernel will interrupt the rogue process after a short time (a so-called time-slice), put it on a queue of “runnable” (but not running) processes, and give another process from that queue a chance to run.

.

.

On a multi-processor (say, N) system, when you run N instances of the program, the entire system will come to a halt because each processor is saturated with one such idiot.

.

.

On a multi-processor system, the OS will increase the CPU frequency to satisfy the needs of its users. This can lead to not only severe damage of the CPU, but also your house may burn down.

.

.

Permissions (/dev/i2c-1)#

$ ls -l /dev/i2c-1
$ crw-rw---- 1 root i2c 254, 0 Jun 15 22:52 /dev/i2c-1

/dev/i2c-1 is a character device which permits userspace programs to communicate with I2C devices that are present on the local computer. One is able to write a program that opens /dev/i2c-1 just like any other, regular, file. Requests to the device are sent onto the I2C bus using the write() system call, where responses are received using read().

Lets say that …

  • Inside the project that builds the I2C-speaking application, there exists a strong requirement that never, ever a program must run as root. Rather, all programs that belong to the application must run under the credentials of the user application.

  • Currently, the credentials of user application are

    $ id application
    uid=1001(application) gid=1001(application) groups=1001(application)
    

For a program that will run as user application, and that intends to communicate with an I2C device, consider the viability of each of the following options.

Statement

True

False

Why

Run the program as root. This will solve the /dev/i2c-1 permission problem.

.

.

Btw., running a program as root can have severe consequences. If the program contains malicious (or only buggy) code, it might be able to format your disk, for example.

.

.

Ask the administrator of the system that will host the application to add the user application to the i2c group

.

.

Convince your manager to spend $$ on Anthropic Mythos 5 to find another exploitable Linux kernel bug. Embed the generated exploit into the application to impersonate user application as root on every startup of the application.

.

.

Permissions (/etc/passwd)#

/etc/passwd is the user database on Linux/UNIX systems (there are other kinds of user database on Linux too, but /etc/passwd has been there since the Epoch).

Lets say the relevant metadata of /etc/passwd are as follows:

$ ls -l /etc/passwd
-rw-r--r--. 1 root root 3401 Apr 22 12:16 /etc/passwd

And the following program is compiled and run as user application (the same as in Permissions (/dev/i2c-1))

#include <fcntl.h>

int main()
{
    int fd = open("/etc/passwd", O_WRONLY|O_TRUNC);
    if (fd == -1)
        return 1;
    else
        return 0;
}

Statement

True

False

Why

The process will be able to open /etc/passwd for writing, but truncating the file will fail, and the exit code will silently be 0 (disregarding the truncate failure)

.

.

The exit code of the program is 1

.

.

The exit code of the program is 0

.

.

Polymorphic Types (Subtle)#

The following program contains a class hierarchy which exhibits a classic interface/implementation kind of design (though its functionality is rather pointless).

#include <iostream>

class AnyFoo
{
public:
    virtual ~AnyFoo() = default;
    virtual int do_something() const = 0;
};

class SpecialFoo : public AnyFoo
{
public:
    int do_something() const override { return 42; }
};

class BoringFoo : public AnyFoo
{
public:
    int do_something() const override { return 7; }
};

int main()
{
    AnyFoo foo;
    std::cout << foo.do_something() << std::endl;
    return 0;
}

Consider the truth values of each of the following statements about that program.

Statement

True

False

Why

The output of the program, once compiled, is

$ ./program
666

.

.

The program crashes because AnyFoo::do_something() is abstract

.

.

The program does not compile because AnyFoo::do_something() is abstract

.

.

Your computer will turn in to an expensive brick because AnyFoo::do_something() ends up referring to a function inside the kernel that makes the hardware watchdog bite the power management unit

.

.

Polymorphic Types (Even More Subtle)#

Here is a variation of the program from Stray Pointers which makes use of commandline arguments and varies its execution behaviour depending on an argument that it requires.

#include <iostream>

class AnyFoo
{
public:
    virtual ~AnyFoo() = default;
    virtual int do_something() const = 0;
};

class SpecialFoo : public AnyFoo
{
public:
    int do_something() const override { return 42; }
};

class BoringFoo : public AnyFoo
{
public:
    int do_something() const override { return 7; }
};

int main(int argc, char** argv)
{
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " (special|boring)" << std::endl;
        return 1;
    }
    std::string whichone = argv[1];

    SpecialFoo special_foo;
    BoringFoo boring_foo;
    AnyFoo* foo;

    if (whichone == "special")
        foo = &special_foo;
    else if (whichone == "boring")
        foo = &boring_foo;

    std::cout << foo->do_something() << std::endl;
    
    return 0;
}

Be program the name of the resulting executable. Consider the truth values of each of the following statements about that program.

Answer

True

False

Why

When called like ./program boring, the program’s output is 42

.

.

When called like ./program boring, the program’s output is 7

.

.

When called like ./program inferior, the program’s output is 666

.

.

When called like ./program inferior, the program crashes

.

.

When called like ./program inferior, the program does not compile

.

.