Compare commits
32 Commits
new-dialog
...
77775e4097
Author | SHA1 | Date | |
---|---|---|---|
77775e4097
|
|||
49f4612c6e
|
|||
7dac50e4d9
|
|||
8ca44dfab3
|
|||
1b53a9638e
|
|||
5dff492663
|
|||
3566131705
|
|||
6423e3b2ef
|
|||
5c92ece0db
|
|||
8700f4f5a2
|
|||
0df75ee195
|
|||
467087d76e
|
|||
448c862731
|
|||
b550c93884
|
|||
a7e5d7d92d
|
|||
56165c0b76
|
|||
b1ee452890
|
|||
8e1c325f98
|
|||
754a26884c
|
|||
2f82ab63ac
|
|||
90d94c7615
|
|||
a1ba96bf67
|
|||
e4dbc5becc
|
|||
33f55384bc
|
|||
c8f19fe30d
|
|||
4febeb7a82
|
|||
c7ec5819c6
|
|||
31f6cc6ab8
|
|||
d4a2cb3749
|
|||
bd4cedf996
|
|||
2a1e94f054
|
|||
fb18484aa8 |
25
CONTENTS.md
Normal file
25
CONTENTS.md
Normal file
@@ -0,0 +1,25 @@
|
||||
The source code is available in `src` directory:
|
||||
|
||||
* `main.c` - main function.
|
||||
* `cc.h` - Convenient Containers library.
|
||||
* `fuse_operations.c`, `fuse_operations.h` - Implementation of FUSE operations.
|
||||
* `perm_permissions_table.c`, `perm_permissions_table.h` - implementation of permanent permissions table.
|
||||
* `proc_operations.c`, `proc_operations.c` - different utility functions that interface with procfs.
|
||||
* `temp_permissions_table.c` `temp_permissions_table.h` - implementation of temporary permission table.
|
||||
* `sourcefs.c`, `sourcefs.h`, `real_filename.h` - operations used to access the underlying filesystem.
|
||||
* `ui-socket.c` `ui-socket.h` - permission logic and interaction with access dialogues.
|
||||
* `gui/` directory - source code of the access dialogue.
|
||||
* Other files are dedicated to special data type definitions.
|
||||
|
||||
`LICENSE` is the text of GPLv2 source code license.
|
||||
|
||||
`Makefile` is code for the `make` build system.
|
||||
|
||||
Tests are located in the `test` directory:
|
||||
|
||||
* `test.bash` - script that tests ICFS correctness (see `README` for usage instructions)
|
||||
* `stress.bash` - helper script that loads ICFS with `open` operations.
|
||||
* `opener/` directory - source code for a scpecial program that tests whether file can be opened.
|
||||
* `mock/` directory - bash script that mocks behaviour of access dialogue.
|
||||
|
||||
After building the program, make is going to create `build` directory where icfs and access dialogue executables will be located.
|
43
Makefile
43
Makefile
@@ -14,6 +14,8 @@ ifndef ($(BUILD_DIR))
|
||||
BUILD_DIR := ./build
|
||||
endif
|
||||
|
||||
|
||||
|
||||
CC := gcc
|
||||
CXX := g++
|
||||
|
||||
@@ -36,7 +38,7 @@ CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES))
|
||||
LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES))
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
CFLAGS += -O0 -pedantic -g -Wall -Wextra -Wcast-align \
|
||||
CFLAGS += -Og -pedantic -g -Wall -Wextra -Wcast-align \
|
||||
-Wcast-qual -Wdisabled-optimization -Wformat=2 \
|
||||
-Winit-self -Wlogical-op -Wmissing-declarations \
|
||||
-Wmissing-include-dirs -Wredundant-decls -Wshadow \
|
||||
@@ -65,10 +67,13 @@ endif
|
||||
|
||||
default: $(TARGETS)
|
||||
|
||||
.PHONY: clean icfs_test clean-icfs clean-icfs_dialogue
|
||||
.PHONY: clean icfs_test clean-icfs clean-icfs_dialogue install uninstall
|
||||
|
||||
$(BUILD_DIR):
|
||||
if [[ ! -d "FILE" ]]; then mkdir $(BUILD_DIR) fi
|
||||
|
||||
$(BUILD_DIR)/icfs_dialogue:
|
||||
make -C $(SOURCES_DIR)/gui TEST=$(TEST) DEBUG=$(shell realpath $(DEBUG)) SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_DIR))
|
||||
make -C $(SOURCES_DIR)/gui TEST=$(TEST) DEBUG=$(shell realpath $(DEBUG)) SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_DIR)) $(BUILD_DIR)
|
||||
|
||||
$(BUILD_DIR)/icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o $(BUILD_DIR)/temp_permissions_table.o $(BUILD_DIR)/perm_permissions_table.o $(BUILD_DIR)/proc_operations.o
|
||||
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs
|
||||
@@ -76,28 +81,28 @@ $(BUILD_DIR)/icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DI
|
||||
icfs_test: $(BUILD_DIR)/icfs
|
||||
cd ./test && ./test.bash
|
||||
|
||||
$(BUILD_DIR)/test_access_control.o: $(TESTS_DIR)/test_access_control.c
|
||||
$(BUILD_DIR)/test_access_control.o: $(TESTS_DIR)/test_access_control.c $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(BUILD_DIR)/main.o: $(SOURCES_DIR)/main.c
|
||||
$(BUILD_DIR)/main.o: $(SOURCES_DIR)/main.c $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $(BUILD_DIR)/main.o
|
||||
|
||||
$(BUILD_DIR)/fuse_operations.o: $(SOURCES_DIR)/fuse_operations.c $(SOURCES_DIR)/fuse_operations.h
|
||||
$(BUILD_DIR)/fuse_operations.o: $(SOURCES_DIR)/fuse_operations.c $(SOURCES_DIR)/fuse_operations.h $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(BUILD_DIR)/sourcefs.o: $(SOURCES_DIR)/sourcefs.c $(SOURCES_DIR)/sourcefs.h $(SOURCES_DIR)/real_filename.h
|
||||
$(BUILD_DIR)/sourcefs.o: $(SOURCES_DIR)/sourcefs.c $(SOURCES_DIR)/sourcefs.h $(SOURCES_DIR)/real_filename.h $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h
|
||||
$(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $(SOURCES_DIR)/temp_permissions_table.h
|
||||
$(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $(SOURCES_DIR)/temp_permissions_table.h $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(BUILD_DIR)/perm_permissions_table.o: $(SOURCES_DIR)/perm_permissions_table.c $(SOURCES_DIR)/perm_permissions_table.h
|
||||
$(BUILD_DIR)/perm_permissions_table.o: $(SOURCES_DIR)/perm_permissions_table.c $(SOURCES_DIR)/perm_permissions_table.h $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(BUILD_DIR)/proc_operations.o: $(SOURCES_DIR)/proc_operations.c $(SOURCES_DIR)/proc_operations.h
|
||||
$(BUILD_DIR)/proc_operations.o: $(SOURCES_DIR)/proc_operations.c $(SOURCES_DIR)/proc_operations.h $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
CLEAN_TARGETS=clean-icfs
|
||||
@@ -113,3 +118,19 @@ clean-icfs:
|
||||
|
||||
clean-icfs_dialogue:
|
||||
make -C $(SOURCES_DIR)/gui clean SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_DIR))
|
||||
|
||||
install: $(BUILD_DIR)/icfs $(BUILD_DIR)/icfs_dialogue
|
||||
@echo "Install script needs superuser permission to:"
|
||||
@printf "\t1. Move executables to /usr/bin.\n"
|
||||
@printf "\t2. Create \"icfs\" user.\n"
|
||||
@printf "\t3. Set the setuid bit of icfs executable.\n"
|
||||
sudo cp $(BUILD_DIR)/icfs /usr/bin/icfs && sudo cp $(BUILD_DIR)/icfs_dialogue /usr/bin/icfs_dialogue
|
||||
id -u icfs &>/dev/null || sudo useradd --system --user-group icfs
|
||||
sudo chown icfs: /usr/bin/icfs && sudo chmod 4777 /usr/bin/icfs
|
||||
@read -p "Create /etc/icfs directory for permission databases [y/N]: " permd; if [[ $$permd == "y" ]]; then echo "sudo mkdir /etc/icfs && sudo chown :icfs /etc/icfs && sudo chmod g+rw,o= /etc/icfs;"; sudo mkdir /etc/icfs && sudo chown icfs:icfs /etc/icfs && sudo chmod g+rw,o= /etc/icfs; fi
|
||||
|
||||
uninstall:
|
||||
@echo "Install script needs superuser permission to remove executables in /usr/bin"
|
||||
sudo rm -f /usr/bin/icfs /usr/bin/icfs_dialogue
|
||||
@read -p "Remove /etc/icfs directory [y/N]: " permd; if [[ $$permd == "y" ]]; then echo "sudo rm -rf /etc/icfs"; sudo rm -rf /etc/icfs; fi
|
||||
|
||||
|
27
README.md
27
README.md
@@ -15,20 +15,37 @@ Traditional access control mechanisms in operating systems allow the same level
|
||||
- Install dependencies
|
||||
- libfuse3
|
||||
- Debian: `sudo apt install fuse3 libfuse3-dev`
|
||||
- zenity
|
||||
- Debian: `sudo apt install zenity`
|
||||
- Build tools
|
||||
- Debian: `sudo apt install gcc make pkg-config`
|
||||
- Build using `make`:
|
||||
- In the project directory: `make`
|
||||
- Use `make DEBUG=1` for testing.
|
||||
- Add `DEBUG=1` to show more compiler warnings.
|
||||
- Add `TEST=1` to also test the program.
|
||||
- Add `DIALOGUE=0` to not compile the dialogue program.
|
||||
- Resulting binaries should appear in the `build` directory.
|
||||
|
||||
## Installation
|
||||
|
||||
- `make install`
|
||||
- Uninstall with `make uninstall`
|
||||
|
||||
## Usage
|
||||
|
||||
`icfs <FUSE arguments> [target directory]`
|
||||
```
|
||||
icfs <FUSE arguments> [target directory] [path to permanent permission database]
|
||||
```
|
||||
|
||||
The filesystem will be mounted over the target directory, and ask user permission every time a file in that directory is opened.
|
||||
The filesystem will be mounted over the target directory, and ask user permission every time a file in that directory is opened. We highly recommend adding `-o default_permissions` to increase performance and add an additional security layer. If you have installed icfs along with `/etc/icfs` folder, you can create your permanent permission databases in this folder (you might want to do this, if your home folder does not have the "execute" permission for other users).
|
||||
|
||||
### Development build
|
||||
|
||||
Execute this command in the root directory of this project:
|
||||
|
||||
```
|
||||
env PATH="$(realpath ./build):$PATH" build/icfs <FUSE arguments> [target directory] [path to permanent permission database]
|
||||
```
|
||||
|
||||
The `env PATH="$(realpath ./build):$PATH"` adds the access dialogue program to PATH, allowing ICFS to call it seamlessly.
|
||||
|
||||
## Docs
|
||||
|
||||
|
@@ -1,66 +0,0 @@
|
||||
# File subsystem with process-specific file access control for Linux.
|
||||
## The problem
|
||||
When you run a program on Linux, it can access the same files as the user account that started it, which is usually overly permissive.
|
||||
|
||||
For example, one may write a trojan, that would download all of your photos, documents, personal info, data of other programs, etc. and you wouldn't even have a way of knowing it happened.
|
||||
|
||||
At the same time, you *sometimes* would like *some* programs to have access to *some* of your files, for example if you are applying for the university, you would like your text editor to have access to application form, but you only need it when you are applying for a university, and you only need the editor to access this specific file.
|
||||
|
||||
So you have to somehow control *when* and *which* programs can access *what* file.
|
||||
|
||||
## Existing file access control solutions
|
||||
|
||||
### Linux security modules
|
||||
Kernel modules such as SELinux and AppArmor can certainly enforce rules on *which* programs can access *what* files, which certainly helps. Unfortunately:
|
||||
* They are overcomplicated (especially SELinux).
|
||||
* They have to be compiled into the kernel.
|
||||
* They give permissions forever: you have to reset them manually.
|
||||
|
||||
### Flatpaks, Snaps and similar namespace-based solutions
|
||||
When the programs themselves are isolated from the rest of the OS, it definitely helps to prevent unwanted access to the filesystem. However:
|
||||
|
||||
* To even be able to override the default filesystem access permissions the user needs to use another app (flatseal for example) or to master the CLI tools.
|
||||
* They give permissions forever: you have to revoke them manually.
|
||||
* The program has to be packaged and distributed is a specific way, which is a developer's decision.
|
||||
* **LOTS of apps need access to some files. Because the default permissions are regulated by the developer himself, it is usually easier to give entire filesystem access permissions, rather than select a specific folder that it needs.**
|
||||
|
||||
## My idea to solve the problem
|
||||
Make a program using FUSE that would control processes' access to selected files by freezing the process and letting the user to choose whether to allow the access or not.
|
||||
|
||||
From the point of view of the process files would appear completely normal, so no involvement of the developer is required.
|
||||
|
||||
When a process calls `open()` on a file that our system controls, that process would be frozen. User then uses the system's interface to either allow (and then the file is opened normally), or deny access (and then the `open()` returns `EACCES`).
|
||||
|
||||
Under the hood, the system's daemon would open some other file stored in a hidden directory with restricted "classic" POSIX permissions (I talk more about how those files are protected in the problems section). From now on, these files would be called "source files".
|
||||
|
||||
The program would keep track processes' permissions, and allow repeated access to the same file, to avoid bothering the user excessively.
|
||||
|
||||
Also, because the program is running fully within userspace, it will make it far easier to develop, interface with, install and distribute.
|
||||
|
||||
## Problems
|
||||
|
||||
See `bc-thesis-problems.md` .
|
||||
|
||||
## (Proposed) time plan
|
||||
* before 31.10 - A sort of "security risk assessment" and therefore a review of requirements, since this kind of is a security project.
|
||||
* before 30.11 - Architecture and design.
|
||||
* before 28.01 - Implementation.
|
||||
* before 31.02 - Testing.
|
||||
* before 30.03 - The thesis itself.
|
||||
|
||||
Rest of the time is reserved for unexpected problems.
|
||||
|
||||
Since the "buisness analysis" and requirements are already more than half-ready, I am not including them.
|
||||
|
||||
## Formal specification
|
||||
Name:
|
||||
|
||||
`File subsystem with temporary, process-specific file access control policies for Linux.`
|
||||
|
||||
Goal:
|
||||
|
||||
`Develop and implement a virtual file subsystem that would manage and enforce temporary, process and file specific rules and policies regulated by the user's choice, therefore ensuring greater security by restricting arbitrary file access by processes.`
|
||||
|
||||
[^1]: I haven't found any better information on that topic. The solutions mentioned seem overcomplicated.
|
||||
[^2]: Which is obtained through 22-nd field in /proc/\[pid\]/stat (see `man proc_pid_stat(5)`).
|
||||
[^3]: To the best of my knowledge.
|
@@ -1,78 +0,0 @@
|
||||
|
||||
# Problems
|
||||
## Keeping track of running processes
|
||||
Since [we can't simply be notified about random process's termination](https://stackoverflow.com/questions/15275366/how-to-get-notified-on-termination-of-some-another-process-in-linux)[^1], the question arises: how to keep track of the processes in the permission table?
|
||||
If we don't delete a permission table entry exactly before the process finishes, another process, [intentionally](https://stackoverflow.com/questions/18122592/how-to-set-process-id-in-linux-for-a-specific-program) or not, gets the same PID, it would gain unauthorized access to files.
|
||||
|
||||
The solution I offer involves writing down process's start time:
|
||||
|
||||
* Permission table will consist of entries $(pid, starttime, permission)$, where $pid$ is the process's PID, $starttime$ is the process's start time and $permission$ is the type of access the process is allowed (e.g. to which files).
|
||||
* When the process opens a file, search through the table.
|
||||
+ If an entry with the correct PID was found:
|
||||
- If $starttime_{table} < starttime_{actual}$[^2], delete all the entries with that PID and file a permission request to the user -- the process that requested access is not the same because it has started after the original process who had this permission(and because their PIDs match, the original process had to end before this could start).
|
||||
- Otherwise:
|
||||
* If the permission matches the request, open the file.
|
||||
* Otherwise file a new permission request.
|
||||
+ Otherwise file a new permission request.
|
||||
|
||||
This approach should make "permission hijacking" practically impossible, as it would take a considerable amount of time just to approve the permission. Even if our permissions could be approved instantly it would still take a few scheduler cycles to execute the original program, so the new ones must have a later start time.
|
||||
|
||||
This approach still has an issue: the table would be hoarded with old permissions, and therefore waste memory. This might be mitigated by regular cleanups (e.g. every time you create a new permission, check if the oldest one's process is still running)
|
||||
|
||||
Another issue might be speed, because all of the table lookups take linear time. But considering that file opening isn't exactly a time-critical operation and that the table could be sorted by PID, it does not seem to be that big of an issue.
|
||||
|
||||
## FUSE issues
|
||||
As mentioned in the initial idea, the source files have to be protected from external access in some way.
|
||||
|
||||
Here I can see three solutions:
|
||||
|
||||
1. Encrypting source files.
|
||||
2. Running our virtual filesystem daemon as a special user, and making all our source files only accessible to this special user, therefore processes would have no permissions to access the source files.
|
||||
3. Use some LSM's MAC policy to deny access to source files to any process except our daemon.
|
||||
|
||||
The first solution would definitely be preferable from the standpoint of compatibility, as it requires no special configuration, and because it mitigates the issue the best, as the attacker has to get encryption keys from the memory. Moreover, it could allow us to deny access for processes running as `root`[^3]. However, implementing encryption seems to be way out of scope of this project.
|
||||
|
||||
The second solution seems to work well in theory. There are some specific details to be clarified about it's implementation, but generally it seems to work out.
|
||||
|
||||
Although the third solution looks like exactly what we are looking for, but it requires a kernel module that enforces MAC policies. Even though those modules are common on most distros, they are pretty different from each other, and I think their usage should be limited to being an additional layer on top of the second solution.
|
||||
|
||||
## User interface
|
||||
From the description of the software, it remains unclear what type of user interaction design is appropriate. Do we make a CLI? GUI?
|
||||
|
||||
I propose to deal with it like the [Transmission](https://github.com/transmission/transmission) did[^3] -- create the backend with an integrated CLI first, and then make GUI wrapper in GTK after (if there would be time, of course `:)` ).
|
||||
|
||||
## Programs with CLI
|
||||
Programs that are run via shell can have an execution time of a fraction of a second, which means you would have to re-allow the access for them every time you run them.
|
||||
My solution is to give an option to set permissions for specific *sessions*, so that every command run from a specific terminal window would have the same permissions.
|
||||
Then the permission table would be populated with the session process's data (e.g. the shell), or maybe this would necessitate a separate table just for the sessions.
|
||||
That might also imply a creation of two CLI tools:
|
||||
|
||||
* `sprequest [PERM]` -- request a certain permission for the session
|
||||
* `spdrop` -- drop all permissions given to the session
|
||||
|
||||
## Too many files...
|
||||
It would be annoying to go and allow access for each program every time one of them opens a new file.
|
||||
For example, if the file subsystem looks like this:
|
||||
```
|
||||
Documents/
|
||||
| Work/
|
||||
| | horalky_secret_recipe.pdf
|
||||
| | colleagues.csv
|
||||
| Notes/
|
||||
| | set_theory.md
|
||||
| | Peano_axioms.md
|
||||
| | ...
|
||||
| | Cantor_theorem.md
|
||||
```
|
||||
If I want to view my notes, I would want allow my markdown editor the access to all the files in the `Documents/Notes` folder to make links work, but when I want to find a name of my colleague, I would like to only give the permission to the `Documents/Work/colleagues.csv` file.
|
||||
The solution is obvious: to give an option for the user to give permissions for the entire folder, not just a specific file.
|
||||
|
||||
## Other
|
||||
* Threads (not that big of a problem, but a complication nevertheless).
|
||||
* All of the permissions get reset after the process is ended, which is annoying. Perhaps it would be possible to implement permanent permissions through identifying programs by their executables' hashes/paths, but updates almost certainly make that impossible (unless we are modifying a package manager, which is clearly out of scope of this project).
|
||||
|
||||
Another issue, severity of which I can't assess properly, is whether this idea fits the Informatics degree, as it does not seem to be as much of an experimental or "theoretical" idea. In my opinion it sill holds enough water to entertain the possibility though.
|
||||
|
||||
[^1]: I haven't found any better information on that topic. The solutions mentioned seem overcomplicated.
|
||||
[^2]: Which is obtained through 22-nd field in /proc/\[pid\]/stat (see `man proc_pid_stat(5)`).
|
||||
[^3]: To the best of my knowledge.
|
@@ -1,33 +0,0 @@
|
||||
# Formal specification
|
||||
|
||||
## Name
|
||||
```
|
||||
Filesystem with Interactive Access Control for Linux
|
||||
```
|
||||
|
||||
```
|
||||
Súborový systém s interaktívnym riadením prístupu pre Linux
|
||||
```
|
||||
|
||||
## Note
|
||||
```
|
||||
Traditional access control mechanisms in operating systems allow the same level of access to all processes running on behalf of the same user. This typically enables malicious processes to read and/or modify all data accessible to the user running a vulnerable application. It can be dealt using various mandatory access control mechanisms, but these are often complicated to configure and are rarely used in common user oriented scenarios. This thesis focuses on design and implementation of a filesystem layer which delegates the decision to allow or deny access to a filesystem object by a specific process to the user.
|
||||
```
|
||||
|
||||
```
|
||||
Tradičné mechanizmy riadenia prístupu v operačných systémoch povoľujú rovnakú úroveň prístupu všetkým procesom bežiacim v mene toho istého používateľa. Toto typicky umožňuje škodlivým procesom čítať a/alebo modifikovať všetky údaje prístupné používateľovi, ktorý spustil zraniteľnú aplikáciu. Dá sa to riešiť použitím rôznych mechanizmov povinného riadenia prístupu, no tieto sú často náročné na konfiguráciu a zriedkavo sa používajú v bežných scenároch orientovaných na používateľa. Táto práca sa zameriava na návrh a implementáciu vrstvy súborového systému, ktorá rozhodnutie povoliť alebo zakázať prístup k objektu súborového systému konkrétnym procesom deleguje na používateľa.
|
||||
```
|
||||
|
||||
## Goal
|
||||
|
||||
```
|
||||
- analyse the problem and design a solution
|
||||
- implement the solution using the FUSE framework
|
||||
- test the solution and demonstrate its benefits
|
||||
```
|
||||
|
||||
```
|
||||
- analyzovať problém a navrhnúť riešenie
|
||||
- implementovať riešenie využitím FUSE
|
||||
- otestovať riešenie a demonštrovať jeho prínos
|
||||
```
|
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
ICFS: Interactively Controlled File System
|
||||
Copyright (C) 2024-2025 Fedir Kovalov
|
||||
|
||||
This program can be distributed under the terms of the GNU GPLv2.
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#ifndef ACCESS_T_H
|
||||
#define ACCESS_T_H
|
||||
|
||||
|
@@ -11,7 +11,9 @@
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#include "process_info.h"
|
||||
#include "real_filename.h"
|
||||
#include "set_mode_t.h"
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#define FUSE_USE_VERSION 31
|
||||
@@ -41,8 +43,13 @@
|
||||
#include "fuse_operations.h"
|
||||
#include "proc_operations.h"
|
||||
#include "sourcefs.h"
|
||||
#include "temp_permissions_table.h"
|
||||
#include "ui-socket.h"
|
||||
|
||||
int auto_create_perm = GRANT_TEMP;
|
||||
|
||||
void set_auto_create_perm(int val) { auto_create_perm = val; }
|
||||
|
||||
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
|
||||
(void)conn;
|
||||
cfg->use_ino = 1;
|
||||
@@ -53,7 +60,9 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
|
||||
in current function (recommended in high level API) or set fi->direct_io
|
||||
in xmp_create() or xmp_open(). */
|
||||
cfg->direct_io = 1;
|
||||
#if FUSE_VERSION > FUSE_MAKE_VERSION(3, 14)
|
||||
cfg->parallel_direct_writes = 1;
|
||||
#endif
|
||||
|
||||
/* Pick up changes from lower filesystem right away. This is
|
||||
also necessary for better hardlink support. When the kernel
|
||||
@@ -67,6 +76,7 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
|
||||
cfg->negative_timeout = 0;
|
||||
fprintf(stderr, "%d\n", getpid());
|
||||
assert(get_mountpoint() != NULL);
|
||||
init_garbage_collector();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -82,7 +92,7 @@ static int xmp_getattr(const char *path, struct stat *stbuf,
|
||||
else
|
||||
res = source_stat(path, stbuf);
|
||||
if (res == -1) {
|
||||
perror("Stat failed");
|
||||
perror("[ICFS] Stat failed");
|
||||
return -errno;
|
||||
}
|
||||
|
||||
@@ -146,7 +156,7 @@ static int xmp_opendir(const char *path, struct fuse_file_info *fi) {
|
||||
|
||||
d->dp = source_opendir(path);
|
||||
if (d->dp == NULL) {
|
||||
perror("Opendir failed");
|
||||
perror("[ICFS] Opendir failed");
|
||||
res = -errno;
|
||||
free(d);
|
||||
return res;
|
||||
@@ -265,8 +275,7 @@ static int xmp_unlink(const char *path) {
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
// ask the user for the permission for deleting the file
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
pi = get_process_info(fc->pid);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
|
||||
@@ -313,8 +322,7 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
pi = get_process_info(fc->pid);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
|
||||
@@ -344,8 +352,7 @@ static int xmp_link(const char *from, const char *to) {
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
pi = get_process_info(fc->pid);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
if (!interactive_access(from, pi, 0)) {
|
||||
@@ -369,8 +376,7 @@ static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
pi = get_process_info(fc->pid);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
if (!interactive_access(path, pi, 0)) {
|
||||
@@ -400,8 +406,7 @@ static int xmp_chown(const char *path, uid_t uid, gid_t gid,
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
pi = get_process_info(fc->pid);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
if (!interactive_access(path, pi, 0)) {
|
||||
@@ -459,14 +464,15 @@ static int xmp_create(const char *path, mode_t mode,
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
pi = get_process_info(fc->pid);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
|
||||
if (!interactive_access(path, pi, GRANT_PERM)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
if (auto_create_perm != 0) {
|
||||
if (!interactive_access(path, pi, auto_create_perm)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
}
|
||||
|
||||
free(pi.name);
|
||||
@@ -484,8 +490,7 @@ static int xmp_open(const char *path, struct fuse_file_info *fi) {
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
pi = get_process_info(fc->pid);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
if (!interactive_access(path, pi, 0)) {
|
||||
|
@@ -14,5 +14,6 @@
|
||||
#include <fuse3/fuse.h>
|
||||
|
||||
const struct fuse_operations *get_fuse_operations();
|
||||
void set_auto_create_perm(int val);
|
||||
|
||||
#endif
|
||||
|
@@ -7,9 +7,10 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define YES 0
|
||||
#define NO 1
|
||||
#define YES 1
|
||||
#define NO 0
|
||||
#define PERM 2
|
||||
#define TEMP 0
|
||||
|
||||
int exit_code = 0;
|
||||
gboolean is_permanent = false;
|
||||
@@ -20,7 +21,7 @@ static void positive_response(GtkWindow *window) {
|
||||
fprintf(stdout, "%s", gtk_entry_buffer_get_text(entry_buffer));
|
||||
exit_code = (gtk_check_button_get_active(GTK_CHECK_BUTTON(checkbox)))
|
||||
? YES | PERM
|
||||
: YES;
|
||||
: YES | TEMP;
|
||||
gtk_window_close(window);
|
||||
}
|
||||
|
||||
@@ -28,7 +29,7 @@ static void negative_response(GtkWindow *window) {
|
||||
fprintf(stdout, "%s", gtk_entry_buffer_get_text(entry_buffer));
|
||||
exit_code = (gtk_check_button_get_active(GTK_CHECK_BUTTON(checkbox)))
|
||||
? NO | PERM
|
||||
: NO;
|
||||
: NO | TEMP;
|
||||
gtk_window_close(window);
|
||||
}
|
||||
|
||||
@@ -143,6 +144,9 @@ int main(int argc, char **argv) {
|
||||
fprintf(stdout, "icfs_dialogue 1.0.0");
|
||||
}
|
||||
|
||||
// disable accessibility features to prevent attacks
|
||||
g_setenv("NO_AT_BRIDGE", "1", TRUE);
|
||||
|
||||
// Create a new application
|
||||
AdwApplication *app = adw_application_new("de.umbrasolis.icfs_dialogue",
|
||||
G_APPLICATION_HANDLES_COMMAND_LINE);
|
||||
|
37
src/main.c
37
src/main.c
@@ -10,6 +10,7 @@
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#define FUSE_USE_VERSION 31
|
||||
|
||||
#define _GNU_SOURCE
|
||||
@@ -29,7 +30,28 @@ const char *mountpoint = NULL;
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to "
|
||||
"the permanent permissions database\n");
|
||||
"the permanent permissions database] <ICFS "
|
||||
"arguments>\n\t--no-perm-on-create - do not give any "
|
||||
"access permissions on file creation"
|
||||
"(incompatible with --temp-on-create)\n\t--perm-on-create "
|
||||
"- automatically give permanent access permission to files "
|
||||
"a process creates "
|
||||
"(incompatible with --no-perm-on-create)\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if ((0 == strcmp(argv[argc - 1], "--no-perm-on-create") &&
|
||||
0 == strcmp(argv[argc - 2], "--temp-on-create")) ||
|
||||
(0 == strcmp(argv[argc - 2], "--no-perm-on-create") &&
|
||||
0 == strcmp(argv[argc - 1], "--temp-on-create"))) {
|
||||
fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to "
|
||||
"the permanent permissions database] <ICFS "
|
||||
"arguments>\n\t--no-perm-on-create - do not give any "
|
||||
"access permissions on file creation"
|
||||
"(incompatible with --temp-on-create)\n\t--perm-on-create "
|
||||
"- automatically give permanent access permission to files "
|
||||
"a process creates "
|
||||
"(incompatible with --no-perm-on-create)\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@@ -37,11 +59,20 @@ int main(int argc, char *argv[]) {
|
||||
// permissions than it's caller reqested
|
||||
umask(0);
|
||||
|
||||
if (0 == strcmp(argv[argc - 1], "--no-perm-on-create")) {
|
||||
set_auto_create_perm(0);
|
||||
argc--;
|
||||
}
|
||||
if (0 == strcmp(argv[argc - 1], "--perm-on-create")) {
|
||||
set_auto_create_perm(GRANT_PERM);
|
||||
argc--;
|
||||
}
|
||||
|
||||
// ui socket should always be initialized before anything else, since it
|
||||
// handles the setuid bits!
|
||||
int ret = init_ui_socket(argv[argc - 1]);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Could not initalize ui-socket.\n");
|
||||
fprintf(stderr, "[ICFS] Could not initalize ui-socket.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -49,7 +80,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
ret = source_init(mountpoint);
|
||||
if (ret != 0) {
|
||||
perror("source_init");
|
||||
perror("[ICFS] source_init");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
|
@@ -29,19 +29,19 @@ const char *const table_name = "permissions";
|
||||
const int column_count = 3;
|
||||
const char *const schema[] = {"executable", "filename", "mode"};
|
||||
const char *const types[] = {"TEXT", "TEXT", "INTEGER"};
|
||||
uid_t ruid, euid, current_pid;
|
||||
uid_t ruid, euid, current_uid;
|
||||
pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void set_db_fsuid() {
|
||||
pthread_mutex_lock(&uid_switch);
|
||||
if (current_pid == ruid)
|
||||
if (current_uid == ruid)
|
||||
return;
|
||||
|
||||
int status = -1;
|
||||
|
||||
status = setfsuid(ruid);
|
||||
if (status < 0) {
|
||||
fprintf(stderr, "Couldn't set uid to %d.\n", ruid);
|
||||
fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", ruid);
|
||||
exit(status);
|
||||
}
|
||||
pthread_mutex_unlock(&uid_switch);
|
||||
@@ -49,14 +49,14 @@ void set_db_fsuid() {
|
||||
|
||||
void set_real_fsuid() {
|
||||
pthread_mutex_lock(&uid_switch);
|
||||
if (current_pid == ruid)
|
||||
if (current_uid == ruid)
|
||||
return;
|
||||
|
||||
int status = -1;
|
||||
|
||||
status = setfsuid(ruid);
|
||||
if (status < 0) {
|
||||
fprintf(stderr, "Couldn't set uid to %d.\n", euid);
|
||||
fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", euid);
|
||||
exit(status);
|
||||
}
|
||||
pthread_mutex_unlock(&uid_switch);
|
||||
@@ -67,12 +67,13 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
|
||||
(void)notused;
|
||||
(void)colname;
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Unexpected amount of arguments given to the callback.\n");
|
||||
fprintf(stderr,
|
||||
"[ICFS] Unexpected amount of arguments given to the callback.\n");
|
||||
return 1;
|
||||
}
|
||||
int column_num = atoi(argv[0]);
|
||||
if (column_num >= column_count) {
|
||||
fprintf(stderr, "Table contains unexpected amount of columns.\n");
|
||||
fprintf(stderr, "[ICFS] Table contains unexpected amount of columns.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -80,7 +81,8 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
|
||||
strcmp(types[column_num], argv[2]) == 0) {
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "Column %d does not conform to the schema.\n", column_num);
|
||||
fprintf(stderr, "[ICFS] Column %d does not conform to the schema.\n",
|
||||
column_num);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -88,14 +90,15 @@ static int set_flag(void *flag, int argc, char **argv, char **colname) {
|
||||
(void)colname;
|
||||
|
||||
if (argc < 3) {
|
||||
fprintf(stderr,
|
||||
"Unexpected amount of arguments given to the callback: %d.\n",
|
||||
argc);
|
||||
fprintf(
|
||||
stderr,
|
||||
"[ICFS] Unexpected amount of arguments given to the callback: %d.\n",
|
||||
argc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (atoi(argv[2])) {
|
||||
fprintf(stderr, "Third column was: %s\n", argv[2]);
|
||||
fprintf(stderr, "[ICFS] Third column was: %s\n", argv[2]);
|
||||
*(int *)flag = 1;
|
||||
} else {
|
||||
*(int *)flag = -1;
|
||||
@@ -104,7 +107,7 @@ static int set_flag(void *flag, int argc, char **argv, char **colname) {
|
||||
}
|
||||
|
||||
int create_database_schema() {
|
||||
fprintf(stderr, "Creating table 'permissions'.\n");
|
||||
fprintf(stderr, "[ICFS] Creating table 'permissions'.\n");
|
||||
const char *create_query =
|
||||
"CREATE TABLE permissions(executable TEXT NOT "
|
||||
"NULL, filename TEXT NOT NULL, mode INTEGER NOT NULL);";
|
||||
@@ -112,12 +115,12 @@ int create_database_schema() {
|
||||
int ret = sqlite3_exec(perm_database, create_query, NULL, NULL, &err);
|
||||
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "sqlite3 error: %s\n", err);
|
||||
fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
|
||||
sqlite3_free(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Database created successfully\n");
|
||||
fprintf(stderr, "[ICFS] Database created successfully\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -131,14 +134,14 @@ int ensure_database_schema() {
|
||||
int result = sqlite3_table_column_metadata(
|
||||
perm_database, NULL, table_name, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
if (result == SQLITE_ERROR) {
|
||||
fprintf(stderr, "Table '%s' does not exist.\n", table_name);
|
||||
fprintf(stderr, "[ICFS] Table '%s' does not exist.\n", table_name);
|
||||
if (create_database_schema()) {
|
||||
fprintf(stderr, "Table could not be created.\n");
|
||||
fprintf(stderr, "[ICFS] Table could not be created.\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else if (result != SQLITE_OK) {
|
||||
fprintf(stderr, "Database metadata could not be retrieved.\n");
|
||||
fprintf(stderr, "[ICFS] Database metadata could not be retrieved.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -148,12 +151,12 @@ int ensure_database_schema() {
|
||||
sqlite3_exec(perm_database, pragma, check_table_col_schema, NULL, &err);
|
||||
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "sqlite3 error: %s\n", err);
|
||||
fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
|
||||
sqlite3_free(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Schema is correct.\n");
|
||||
fprintf(stderr, "[ICFS] Schema is correct.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -168,24 +171,25 @@ int init_perm_permissions_table(const char *db_filename) {
|
||||
umask(0077);
|
||||
ruid = getuid();
|
||||
euid = geteuid();
|
||||
fprintf(stderr, "Running with uid: %d, gid: %d\n", euid, getegid());
|
||||
fprintf(stderr, "[ICFS] Running with uid: %d, gid: %d\n", euid, getegid());
|
||||
|
||||
if (sqlite3_open_v2(db_filename, &perm_database,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
|
||||
SQLITE_OPEN_FULLMUTEX,
|
||||
NULL)) {
|
||||
perror("Can't open permanent permissions database");
|
||||
perror("[ICFS] Can't open permanent permissions database");
|
||||
return -1;
|
||||
}
|
||||
umask(0);
|
||||
if (ensure_database_schema()) {
|
||||
fprintf(stderr, "Database schema is not correct.\n");
|
||||
fprintf(stderr, "[ICFS] Database schema is not correct.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int status = seteuid(ruid);
|
||||
if (status < 0) {
|
||||
fprintf(stderr, "Couldn't set euid to ruid during database setup.\n");
|
||||
fprintf(stderr,
|
||||
"[ICFS] Couldn't set euid to ruid during database setup.\n");
|
||||
exit(status);
|
||||
}
|
||||
|
||||
@@ -201,48 +205,42 @@ void destroy_perm_permissions_table(void) { sqlite3_close(perm_database); }
|
||||
* Checks if the process has a permanent access to the file.
|
||||
*
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @param pi: The process information
|
||||
* @return: access status - ALLOW, DENY or NDEF in case if no information was
|
||||
* found
|
||||
*/
|
||||
access_t check_perm_access_noparent(const char *filename,
|
||||
struct process_info pi) {
|
||||
|
||||
char *query = NULL;
|
||||
int ret = asprintf(&query,
|
||||
"SELECT * FROM %s WHERE executable = \'%s\' "
|
||||
"AND ((\'%s\' LIKE CONCAT(filename, \'%%\') AND filename "
|
||||
"GLOB \'*/\') OR filename = \'%s\');",
|
||||
table_name, pi.name, filename, filename);
|
||||
fprintf(stderr, "query: %s\n", query);
|
||||
|
||||
if (ret < 0) {
|
||||
// If asprintf fails, the contents of query are undefined (see man
|
||||
// asprintf). That does not explicitly rule out that query will be a valid
|
||||
// pointer. But the risk of freeing a non-allocated pointer is too much to
|
||||
// justify preparing for this.
|
||||
fprintf(stderr, "Could not create query on access check");
|
||||
perror("");
|
||||
if (pi.name == NULL)
|
||||
return NDEF;
|
||||
}
|
||||
|
||||
char *sqlite_error = NULL;
|
||||
int flag = 0;
|
||||
ret = sqlite3_exec(perm_database, query, set_flag, &flag, &sqlite_error);
|
||||
free((void *)query);
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
|
||||
sqlite3_free(sqlite_error);
|
||||
return NDEF;
|
||||
}
|
||||
access_t ret = NDEF;
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
const char *sql =
|
||||
"SELECT mode FROM permissions WHERE executable = ?1 "
|
||||
"AND (( ?2 LIKE (filename || \'%\') AND filename "
|
||||
"GLOB \'*/\') OR filename = ?2 ) ORDER BY LENGTH( filename ) DESC;";
|
||||
sqlite3_prepare_v2(perm_database, sql, -1, &stmt, NULL);
|
||||
sqlite3_bind_text(stmt, 1, pi.name, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, filename, -1, SQLITE_STATIC);
|
||||
|
||||
if (flag == 1) {
|
||||
return ALLOW;
|
||||
int step_ret = sqlite3_step(stmt);
|
||||
if (step_ret != SQLITE_ROW && step_ret != SQLITE_DONE) {
|
||||
fprintf(stderr, "[ICFS] SQLite error: %s\n", sqlite3_errstr(step_ret));
|
||||
sqlite3_finalize(stmt);
|
||||
return ret;
|
||||
}
|
||||
if (flag == -1) {
|
||||
return DENY;
|
||||
if (step_ret == SQLITE_ROW) {
|
||||
int mode_col = sqlite3_column_int(stmt, 0);
|
||||
if (mode_col) {
|
||||
ret = ALLOW;
|
||||
} else {
|
||||
ret = DENY;
|
||||
}
|
||||
}
|
||||
return NDEF;
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +248,7 @@ access_t check_perm_access_noparent(const char *filename,
|
||||
* file.
|
||||
*
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @param pi: The process information
|
||||
* @return: access status - ALLOW, DENY or NDEF in case if no information was
|
||||
* found. Does not return ALLOW_TEMP or DENY_TEMP.
|
||||
* @note: In case one of the parent processes is killed while this function
|
||||
@@ -258,7 +256,7 @@ access_t check_perm_access_noparent(const char *filename,
|
||||
* false negatives, though.
|
||||
*/
|
||||
access_t check_perm_access(const char *filename, struct process_info pi) {
|
||||
if (pi.PID == 0) {
|
||||
if (pi.PID == 0 || pi.name == NULL) {
|
||||
return NDEF;
|
||||
}
|
||||
|
||||
@@ -272,7 +270,7 @@ access_t check_perm_access(const char *filename, struct process_info pi) {
|
||||
}
|
||||
current_pi.name = NULL;
|
||||
while (current_pi.name == NULL) {
|
||||
current_pi.PID = get_parent_pid(current_pi.PID);
|
||||
current_pi.PID = get_main_thread_pid(get_parent_pid(current_pi.PID));
|
||||
if (current_pi.PID != 0) {
|
||||
current_pi.name = get_process_name_by_pid(current_pi.PID);
|
||||
} else {
|
||||
@@ -293,37 +291,27 @@ access_t check_perm_access(const char *filename, struct process_info pi) {
|
||||
*/
|
||||
int set_perm_access(const char *filename, struct process_info pi,
|
||||
set_mode_t mode) {
|
||||
char *query = NULL;
|
||||
int ret = -1;
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
char *sql = NULL;
|
||||
|
||||
if (mode == SET_ALLOW) {
|
||||
ret = asprintf(&query, "INSERT INTO %s VALUES (\'%s\', \'%s\', TRUE);",
|
||||
table_name, pi.name, filename);
|
||||
sql = "INSERT INTO permissions VALUES (?1, ?2, TRUE);";
|
||||
} else if (mode == SET_DENY) {
|
||||
ret = asprintf(&query, "INSERT INTO %s VALUES (\'%s\', \'%s\', FALSE);",
|
||||
table_name, pi.name, filename);
|
||||
sql = "INSERT INTO permissions VALUES (?1, ?2, FALSE);";
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
// If asprintf fails, the contents of query are undefined (see man
|
||||
// asprintf). That does not explicitly rule out that query will be a valid
|
||||
// pointer. But the risk of freeing a non-allocated pointer is too much to
|
||||
// justify preparing for this.
|
||||
fprintf(stderr, "Could not create query on rule insertion");
|
||||
perror("");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *sqlite_error = NULL;
|
||||
ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error);
|
||||
free(query);
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
|
||||
sqlite3_free(sqlite_error);
|
||||
free(query);
|
||||
sqlite3_prepare_v2(perm_database, sql, -1, &stmt, NULL);
|
||||
sqlite3_bind_text(stmt, 1, pi.name, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, filename, -1, SQLITE_STATIC);
|
||||
int step_ret = sqlite3_step(stmt);
|
||||
if (step_ret != SQLITE_DONE) {
|
||||
fprintf(stderr, "[ICFS] SQLite error: %s\n", sqlite3_errstr(step_ret));
|
||||
sqlite3_finalize(stmt);
|
||||
return 1;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ void destroy_perm_permissions_table();
|
||||
* Checks if the process has a permanent access to the file.
|
||||
*
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @param pi: The process information
|
||||
* @return: access status - ALLOW, DENY or NDEF in case if no information was
|
||||
* found
|
||||
*/
|
||||
@@ -43,7 +43,7 @@ access_t check_perm_access(const char *filename, struct process_info pi);
|
||||
* @param pi: The process information
|
||||
* @param mode: Kind of access rule to be set - SET_DENY to deny access, and
|
||||
* SET_ALLOW to allow access.
|
||||
* @return: 0 on success, -1 on failure
|
||||
* @return: 0 on success, 1 on failure
|
||||
*/
|
||||
int set_perm_access(const char *filename, struct process_info pi,
|
||||
set_mode_t mode);
|
||||
|
@@ -1,16 +1,94 @@
|
||||
/*
|
||||
ICFS: Interactively Controlled File System
|
||||
Copyright (C) 2024-2025 Fedir Kovalov
|
||||
|
||||
This program can be distributed under the terms of the GNU GPLv2.
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#include "proc_operations.h"
|
||||
#include <linux/limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* @brief Returns the PID of the main thread (i.e., the process ID) of the
|
||||
* process that the given thread ID (tid) belongs to.
|
||||
*
|
||||
* @param tid The thread ID (TID) of any thread in the process.
|
||||
* @return pid_t The process ID (main thread's PID), or -1 on error.
|
||||
*/
|
||||
pid_t get_main_thread_pid(pid_t tid) {
|
||||
// Validate input
|
||||
if (tid <= 0) {
|
||||
// errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "/proc/%d/status", tid);
|
||||
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp) {
|
||||
return 0; // Could not open the file
|
||||
}
|
||||
|
||||
pid_t tgid = 0;
|
||||
char line[256];
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
if (sscanf(line, "Tgid: %d", &tgid) == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
if (tgid != tid) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] The tid and and pid wasn't equal. tid:%d, pid:%d.\n", tid,
|
||||
tgid);
|
||||
}
|
||||
return tgid;
|
||||
}
|
||||
|
||||
char *get_process_name_by_pid(const int pid) {
|
||||
char path[1024];
|
||||
sprintf(path, "/proc/%d/exe", pid);
|
||||
|
||||
char *name = realpath(path, NULL);
|
||||
size_t size = 128;
|
||||
char *name = malloc(size);
|
||||
if (name == NULL) {
|
||||
fprintf(stderr, "Could not get process name by pid %d", pid);
|
||||
fprintf(stderr, "[ICFS] Could not get process name by pid %d", pid);
|
||||
perror("");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
ssize_t len = readlink(path, name, size);
|
||||
|
||||
if (len == -1) {
|
||||
fprintf(stderr, "[ICFS] Could not get process name by pid %d", pid);
|
||||
perror("");
|
||||
free(name);
|
||||
return NULL;
|
||||
}
|
||||
if ((size_t)len >= size) {
|
||||
size *= 2;
|
||||
char *new_name = realloc(name, size);
|
||||
if (!new_name) {
|
||||
free(name);
|
||||
return NULL;
|
||||
}
|
||||
name = new_name;
|
||||
} else {
|
||||
// readlink does not set the null character
|
||||
name[len] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -28,7 +106,7 @@ pid_t get_parent_pid(pid_t pid) {
|
||||
|
||||
FILE *file = fopen(path, "r");
|
||||
if (file == NULL) {
|
||||
perror("Failed to open /proc/<pid>/status");
|
||||
perror("[ICFS] Failed to open /proc/<pid>/status");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
ICFS: Interactively Controlled File System
|
||||
Copyright (C) 2024-2025 Fedir Kovalov
|
||||
|
||||
This program can be distributed under the terms of the GNU GPLv2.
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#ifndef PROC_OPERATIONS
|
||||
#define PROC_OPERATIONS
|
||||
|
||||
@@ -14,4 +22,13 @@ char *get_process_name_by_pid(const int pid);
|
||||
*/
|
||||
pid_t get_parent_pid(pid_t pid);
|
||||
|
||||
/**
|
||||
* @brief Returns the PID of the main thread (i.e., the process ID) of the
|
||||
* process that the given thread ID (tid) belongs to.
|
||||
*
|
||||
* @param tid The thread ID (TID) of any thread in the process.
|
||||
* @return pid_t The process ID (main thread's PID), or -1 on error.
|
||||
*/
|
||||
pid_t get_main_thread_pid(pid_t tid);
|
||||
|
||||
#endif // !PROC_OPERATIONS
|
||||
|
@@ -9,10 +9,25 @@
|
||||
#ifndef PROCESS_INFO_H
|
||||
#define PROCESS_INFO_H
|
||||
|
||||
#include "proc_operations.h"
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
struct process_info {
|
||||
pid_t PID;
|
||||
char *name;
|
||||
};
|
||||
|
||||
static inline struct process_info get_process_info(pid_t pid) {
|
||||
struct process_info pi;
|
||||
pi.PID = get_main_thread_pid(pid);
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
if (pi.name == NULL) {
|
||||
pi.PID = 0;
|
||||
}
|
||||
return pi;
|
||||
}
|
||||
|
||||
#endif // PROCESS_INFO_H
|
||||
|
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
ICFS: Interactively Controlled File System
|
||||
Copyright (C) 2024-2025 Fedir Kovalov
|
||||
|
||||
This program can be distributed under the terms of the GNU GPLv2.
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#ifndef REAL_FILENAME_H
|
||||
#define REAL_FILENAME_H
|
||||
|
||||
|
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
ICFS: Interactively Controlled File System
|
||||
Copyright (C) 2024-2025 Fedir Kovalov
|
||||
|
||||
This program can be distributed under the terms of the GNU GPLv2.
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#ifndef SET_MODE_T_H
|
||||
#define SET_MODE_T_H
|
||||
|
@@ -32,7 +32,7 @@ const char *source_filename_translate(const char *filename) {
|
||||
int source_init(const char *root_path) {
|
||||
handle.mountpoint = malloc(strlen(root_path) + 1);
|
||||
if (handle.mountpoint == NULL) {
|
||||
perror("Malloc failed");
|
||||
perror("[ICFS] Malloc failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ int source_init(const char *root_path) {
|
||||
int root_fd = open(root_path, O_PATH);
|
||||
|
||||
if (root_fd == -1) {
|
||||
fprintf(stderr, "Could not initialize source file system at %s", root_path);
|
||||
fprintf(stderr, "[ICFS] Could not initialize source file system at %s",
|
||||
root_path);
|
||||
perror("");
|
||||
return -1;
|
||||
}
|
||||
@@ -65,7 +66,7 @@ const char *real_filename(const char *filename) {
|
||||
// Allocate memory (+1 for null terminator)
|
||||
char *result = malloc(total_len + 1);
|
||||
if (result == NULL) {
|
||||
fprintf(stderr, "Memory allocation failed");
|
||||
fprintf(stderr, "[ICFS] Memory allocation failed");
|
||||
perror("");
|
||||
return NULL;
|
||||
}
|
||||
@@ -111,7 +112,7 @@ DIR *source_opendir(const char *filename) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
int fd = openat(handle.root_fd, relative_filename, 0);
|
||||
if (fd < 0) {
|
||||
perror("Openat failed");
|
||||
perror("[ICFS] Openat failed");
|
||||
return NULL;
|
||||
}
|
||||
DIR *dir_pointer = fdopendir(fd);
|
||||
@@ -148,7 +149,7 @@ int source_truncate(const char *filename, off_t length) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
int fd = openat(handle.root_fd, relative_filename, 0);
|
||||
if (fd < 0) {
|
||||
perror("Openat failed");
|
||||
perror("[ICFS] Openat failed");
|
||||
return -1;
|
||||
}
|
||||
return ftruncate(fd, length);
|
||||
|
@@ -16,6 +16,8 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct temp_process_permissions {
|
||||
// yes, this is a correct type for start time in jiffies (see
|
||||
@@ -26,7 +28,9 @@ struct temp_process_permissions {
|
||||
};
|
||||
|
||||
map(pid_t, struct temp_process_permissions) temp_permissions_table;
|
||||
pthread_mutex_t temp_permissions_table_lock;
|
||||
pthread_rwlock_t temp_permissions_table_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||
pthread_t gc_thread;
|
||||
int is_gc_active = 0;
|
||||
|
||||
/**
|
||||
* Function to get the process creation time (in jiffies) from the proc
|
||||
@@ -48,20 +52,21 @@ unsigned long long get_process_creation_time(pid_t pid) {
|
||||
// Open the status file
|
||||
fp = fopen(path, "r");
|
||||
if (fp == NULL) {
|
||||
perror("fopen");
|
||||
perror("[ICFS] fopen");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read the creation time (the 22nd field in the stat file)
|
||||
for (int i = 1; i < 22; i++) {
|
||||
if (fscanf(fp, "%*s") == EOF) {
|
||||
fprintf(stderr, "Error reading process stat file on the number %d\n", i);
|
||||
fprintf(stderr,
|
||||
"[ICFS] Error reading process stat file on the number %d\n", i);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (fscanf(fp, "%llu", &creation_time) != 1) {
|
||||
fprintf(stderr, "Error reading creation time\n");
|
||||
fprintf(stderr, "[ICFS] Error reading creation time\n");
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
@@ -72,16 +77,70 @@ unsigned long long get_process_creation_time(pid_t pid) {
|
||||
return creation_time;
|
||||
}
|
||||
|
||||
int is_valid(pid_t pid, struct temp_process_permissions *entry) {
|
||||
unsigned long long creation_time = get_process_creation_time(pid);
|
||||
if (creation_time == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (creation_time != entry->creation_time) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *garbage_collector(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
while (is_gc_active) {
|
||||
sleep(1);
|
||||
pthread_rwlock_wrlock(&temp_permissions_table_lock);
|
||||
|
||||
vec(pid_t) blacklist;
|
||||
init(&blacklist);
|
||||
|
||||
for_each(&temp_permissions_table, pid, entry) {
|
||||
if (!is_valid(*pid, entry)) {
|
||||
push(&blacklist, *pid);
|
||||
for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); }
|
||||
cleanup(&entry->allowed_files);
|
||||
for_each(&entry->denied_files, denied_file) { free(*denied_file); }
|
||||
cleanup(&entry->denied_files);
|
||||
}
|
||||
}
|
||||
|
||||
for_each(&blacklist, pid) { erase(&temp_permissions_table, *pid); }
|
||||
|
||||
cleanup(&blacklist);
|
||||
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the temporary permissions table.
|
||||
*
|
||||
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
|
||||
*/
|
||||
int init_temp_permissions_table(void) {
|
||||
pthread_mutex_init(&temp_permissions_table_lock, PTHREAD_MUTEX_DEFAULT);
|
||||
init(&temp_permissions_table);
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Starts the temporary permissions table garbage_collector.
|
||||
*
|
||||
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
|
||||
*/
|
||||
int init_garbage_collector(void) {
|
||||
is_gc_active = 1;
|
||||
if (pthread_create(&gc_thread, NULL, garbage_collector, NULL) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the temporary permissions table.
|
||||
@@ -91,6 +150,11 @@ int init_temp_permissions_table(void) {
|
||||
* screwed.
|
||||
*/
|
||||
void destroy_temp_permissions_table(void) {
|
||||
if (is_gc_active) {
|
||||
is_gc_active = 0;
|
||||
pthread_join(gc_thread, NULL);
|
||||
}
|
||||
|
||||
// free the memory allocated for the table
|
||||
for_each(&temp_permissions_table, entry) {
|
||||
for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); }
|
||||
@@ -102,27 +166,26 @@ void destroy_temp_permissions_table(void) {
|
||||
}
|
||||
|
||||
cleanup(&temp_permissions_table);
|
||||
pthread_mutex_destroy(&temp_permissions_table_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the process has a temporary access to the file.
|
||||
*
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pid: PID of the process
|
||||
* @param pid: PID of the process
|
||||
* @return: access status - ALLOW, DENY or NDEF in case if no information was
|
||||
* found is avaliable
|
||||
*/
|
||||
access_t check_temp_access_noparent(const char *filename, pid_t pid) {
|
||||
// TODO: more efficient locking
|
||||
pthread_mutex_lock(&temp_permissions_table_lock);
|
||||
pthread_rwlock_rdlock(&temp_permissions_table_lock);
|
||||
struct temp_process_permissions *permission_entry =
|
||||
get(&temp_permissions_table, pid);
|
||||
if (permission_entry != NULL) {
|
||||
unsigned long long process_creation_time = get_process_creation_time(pid);
|
||||
if (process_creation_time == 0) {
|
||||
perror("Could not retrieve process creation time");
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
perror("[ICFS] Could not retrieve process creation time");
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
return NDEF;
|
||||
}
|
||||
|
||||
@@ -130,29 +193,35 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
|
||||
// the process is the same as the one that was granted temporary access
|
||||
// to the file
|
||||
size_t filename_len = strlen(filename);
|
||||
access_t ret = NDEF;
|
||||
size_t maxlen = 0;
|
||||
for_each(&permission_entry->denied_files, denied_file) {
|
||||
size_t denied_file_len = strlen(*denied_file);
|
||||
if (strncmp(*denied_file, filename, denied_file_len) == 0 &&
|
||||
((denied_file_len < filename_len &&
|
||||
(*denied_file)[denied_file_len - 1] == '/') ||
|
||||
(denied_file_len == filename_len))) {
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
return DENY;
|
||||
if ((strncmp(*denied_file, filename, denied_file_len) == 0 &&
|
||||
((denied_file_len < filename_len &&
|
||||
(*denied_file)[denied_file_len - 1] == '/') ||
|
||||
(denied_file_len == filename_len))) &&
|
||||
denied_file_len > maxlen) {
|
||||
maxlen = denied_file_len;
|
||||
ret = DENY;
|
||||
}
|
||||
}
|
||||
for_each(&permission_entry->allowed_files, allowed_file) {
|
||||
size_t allowed_file_len = strlen(*allowed_file);
|
||||
if (strncmp(*allowed_file, filename, allowed_file_len) == 0 &&
|
||||
((allowed_file_len < filename_len &&
|
||||
(*allowed_file)[allowed_file_len - 1] == '/') ||
|
||||
(allowed_file_len == filename_len))) {
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
return ALLOW;
|
||||
if ((strncmp(*allowed_file, filename, allowed_file_len) == 0 &&
|
||||
((allowed_file_len < filename_len &&
|
||||
(*allowed_file)[allowed_file_len - 1] == '/') ||
|
||||
(allowed_file_len == filename_len))) &&
|
||||
allowed_file > maxlen) {
|
||||
maxlen = allowed_file_len;
|
||||
ret = ALLOW;
|
||||
}
|
||||
}
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
return NDEF;
|
||||
}
|
||||
|
||||
@@ -161,7 +230,7 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
|
||||
* file.
|
||||
*
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @param pi: The process information
|
||||
* @return: access status - ALLOW, DENY or NDEF in case if no information was
|
||||
* found. Does not return ALLOW_TEMP.
|
||||
* @note: In case one of the parent processes is killed while this function
|
||||
@@ -175,7 +244,7 @@ access_t check_temp_access(const char *filename, struct process_info pi) {
|
||||
if (access != NDEF) {
|
||||
return access;
|
||||
}
|
||||
current_pid = get_parent_pid(current_pid);
|
||||
current_pid = get_main_thread_pid(get_parent_pid(current_pid));
|
||||
}
|
||||
|
||||
return NDEF;
|
||||
@@ -192,7 +261,9 @@ access_t check_temp_access(const char *filename, struct process_info pi) {
|
||||
*/
|
||||
int set_temp_access(const char *filename, struct process_info pi,
|
||||
set_mode_t mode) {
|
||||
pthread_mutex_lock(&temp_permissions_table_lock);
|
||||
if (pi.PID == 0)
|
||||
return NDEF;
|
||||
pthread_rwlock_wrlock(&temp_permissions_table_lock);
|
||||
struct temp_process_permissions *permission_entry =
|
||||
get(&temp_permissions_table, pi.PID);
|
||||
|
||||
@@ -201,8 +272,8 @@ int set_temp_access(const char *filename, struct process_info pi,
|
||||
unsigned long long process_creation_time =
|
||||
get_process_creation_time(pi.PID);
|
||||
if (process_creation_time == 0) {
|
||||
perror("Could not retrieve process creation time");
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
perror("[ICFS] Could not retrieve process creation time");
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -216,7 +287,7 @@ int set_temp_access(const char *filename, struct process_info pi,
|
||||
push(&permission_entry->denied_files, strdup(filename));
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
return 0;
|
||||
}
|
||||
// we have an entry for the process, but the process is different
|
||||
@@ -241,6 +312,6 @@ int set_temp_access(const char *filename, struct process_info pi,
|
||||
|
||||
insert(&temp_permissions_table, pi.PID, new_permission_entry);
|
||||
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
return 0;
|
||||
}
|
||||
|
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
ICFS: Interactively Controlled File System
|
||||
Copyright (C) 2024-2025 Fedir Kovalov
|
||||
|
||||
This program can be distributed under the terms of the GNU GPLv2.
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#ifndef TEMP_PERMISSIONS_TABLE_H
|
||||
#define TEMP_PERMISSIONS_TABLE_H
|
||||
@@ -13,6 +20,13 @@
|
||||
*/
|
||||
int init_temp_permissions_table(void);
|
||||
|
||||
/**
|
||||
* Starts the temporary permissions table garbage_collector.
|
||||
*
|
||||
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
|
||||
*/
|
||||
int init_garbage_collector(void);
|
||||
|
||||
/**
|
||||
* Destroys the temporary permissions table.
|
||||
*
|
||||
@@ -27,7 +41,7 @@ void destroy_temp_permissions_table(void);
|
||||
* file.
|
||||
*
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @param pi: The process information
|
||||
* @return: access status - ALLOW, DENY or NDEF in case if no information was
|
||||
* found. Does not return ALLOW_TEMP.
|
||||
* @note: In case one of the parent processes is killed while this function
|
||||
|
137
src/ui-socket.c
137
src/ui-socket.c
@@ -26,9 +26,12 @@
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define DIALOGUE_YES 0
|
||||
#define DIALOGUE_NO 1
|
||||
#define DIALOGUE_YES 1
|
||||
#define DIALOGUE_NO 0
|
||||
#define DIALOGUE_PERM 2
|
||||
#define DIALOGUE_TEMP 0
|
||||
|
||||
pthread_mutex_t access_check_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
struct dialogue_response {
|
||||
access_t decision;
|
||||
@@ -39,19 +42,21 @@ int init_ui_socket(const char *perm_permissions_db_filename) {
|
||||
FILE *fp = NULL;
|
||||
|
||||
if (init_temp_permissions_table()) {
|
||||
fprintf(stderr, "Could not initialize temporary permissions table.\n");
|
||||
fprintf(stderr,
|
||||
"[ICFS] Could not initialize temporary permissions table.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (init_perm_permissions_table(perm_permissions_db_filename)) {
|
||||
fprintf(stderr, "Could not initialize permanent permissions table.\n");
|
||||
fprintf(stderr,
|
||||
"[ICFS] Could not initialize permanent permissions table.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test if dialogue is installed (get version)
|
||||
fp = popen("icfs_dialogue --version", "r");
|
||||
if (fp == NULL) {
|
||||
perror("Pipe returned an error");
|
||||
perror("[ICFS] Pipe returned an error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -89,7 +94,7 @@ struct dialogue_response ask_access(const char *filename,
|
||||
// asprintf). That does not explicitly rule out that command will be a valid
|
||||
// pointer. But the risk of freeing a non-allocated pointer is too much to
|
||||
// justify preparing for this.
|
||||
fprintf(stderr, "Could not create query on rule insertion");
|
||||
fprintf(stderr, "[ICFS] Could not create query on rule insertion");
|
||||
perror("");
|
||||
response.decision = DENY;
|
||||
response.filename = malloc(2);
|
||||
@@ -103,7 +108,7 @@ struct dialogue_response ask_access(const char *filename,
|
||||
free(command);
|
||||
|
||||
if (fp == NULL) {
|
||||
perror("Pipe returned a error");
|
||||
perror("[ICFS] Pipe returned a error");
|
||||
response.decision = DENY;
|
||||
response.filename = malloc(2);
|
||||
response.filename[0] = '/';
|
||||
@@ -122,8 +127,9 @@ struct dialogue_response ask_access(const char *filename,
|
||||
}
|
||||
|
||||
int dialogue_exit_code = WEXITSTATUS(pclose(fp));
|
||||
fprintf(stderr, "dialogue wrote out %s\n", first(&dialogue_output));
|
||||
fprintf(stderr, "dialogue returned %d\n", dialogue_exit_code);
|
||||
|
||||
fprintf(stderr, "[ICFS] dialogue wrote out %s\n", first(&dialogue_output));
|
||||
fprintf(stderr, "[ICFS] dialogue returned %d\n", dialogue_exit_code);
|
||||
|
||||
if (size(&dialogue_output) == 0) {
|
||||
push(&dialogue_output, '/');
|
||||
@@ -137,10 +143,11 @@ struct dialogue_response ask_access(const char *filename,
|
||||
|
||||
// assert(0 == strcmp(response.filename, first(&dialogue_output)));
|
||||
cleanup(&dialogue_output);
|
||||
time_t now = time(0);
|
||||
|
||||
if (dialogue_exit_code == (DIALOGUE_YES | DIALOGUE_PERM)) {
|
||||
response.decision = ALLOW;
|
||||
} else if (dialogue_exit_code == DIALOGUE_YES) {
|
||||
} else if (dialogue_exit_code == DIALOGUE_YES | DIALOGUE_TEMP) {
|
||||
response.decision = ALLOW_TEMP;
|
||||
} else if (dialogue_exit_code == (DIALOGUE_NO | DIALOGUE_PERM)) {
|
||||
response.decision = DENY;
|
||||
@@ -158,47 +165,56 @@ struct dialogue_response ask_access(const char *filename,
|
||||
* 3. user descision
|
||||
*
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @param pi: The process information
|
||||
* @param opts: options (GRANT_TEMP, GRANT_PERM)
|
||||
* @return: 0 if access is denied, 1 if access is allowed
|
||||
*/
|
||||
int interactive_access(const char *filename, struct process_info proc_info,
|
||||
int opts) {
|
||||
char *real_path = real_filename(filename);
|
||||
pthread_mutex_lock(&access_check_mutex);
|
||||
|
||||
access_t access = check_temp_access(real_path, proc_info);
|
||||
if (access == ALLOW) {
|
||||
fprintf(stderr,
|
||||
"Permission allowed to %s based on a rule present in the temp "
|
||||
"permission table.\n",
|
||||
proc_info.name);
|
||||
fprintf(
|
||||
stderr,
|
||||
"[ICFS] Permission allowed to %s based on a rule present in the temp "
|
||||
"permission table.\n",
|
||||
proc_info.name);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
return 1;
|
||||
}
|
||||
if (access == DENY) {
|
||||
fprintf(stderr,
|
||||
"Permission denied to %s based on a rule present in the temp "
|
||||
"permission table.\n",
|
||||
proc_info.name);
|
||||
fprintf(
|
||||
stderr,
|
||||
"[ICFS] Permission denied to %s based on a rule present in the temp "
|
||||
"permission table.\n",
|
||||
proc_info.name);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
access = check_perm_access(real_path, proc_info);
|
||||
if (access == ALLOW) {
|
||||
fprintf(stderr,
|
||||
"Permission allowed to %s based on a rule present in the perm "
|
||||
"permission table.\n",
|
||||
proc_info.name);
|
||||
fprintf(
|
||||
stderr,
|
||||
"[ICFS] Permission allowed to %s based on a rule present in the perm "
|
||||
"permission table.\n",
|
||||
proc_info.name);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
return 1;
|
||||
}
|
||||
if (access == DENY) {
|
||||
fprintf(stderr,
|
||||
"Permission denied to %s based on a rule present in the perm "
|
||||
"permission table.\n",
|
||||
proc_info.name);
|
||||
fprintf(
|
||||
stderr,
|
||||
"[ICFS] Permission denied to %s based on a rule present in the perm "
|
||||
"permission table.\n",
|
||||
proc_info.name);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -206,15 +222,19 @@ int interactive_access(const char *filename, struct process_info proc_info,
|
||||
// permissions are granted
|
||||
|
||||
if (opts & GRANT_PERM) {
|
||||
fprintf(stderr, "Permission granted permanently to %s.\n", proc_info.name);
|
||||
fprintf(stderr, "[ICFS] Permission granted permanently to %s.\n",
|
||||
proc_info.name);
|
||||
set_perm_access(real_path, proc_info, SET_ALLOW);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
return 1;
|
||||
}
|
||||
if (opts & GRANT_TEMP) {
|
||||
fprintf(stderr, "Permission granted temporarily to %s.\n", proc_info.name);
|
||||
fprintf(stderr, "[ICFS] Permission granted temporarily to %s.\n",
|
||||
proc_info.name);
|
||||
set_temp_access(real_path, proc_info, SET_ALLOW);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -225,9 +245,15 @@ int interactive_access(const char *filename, struct process_info proc_info,
|
||||
// the user might specify a different file in the dialogue, so we need to
|
||||
// check if it is valid
|
||||
|
||||
while (source_access(response.filename, F_OK)) {
|
||||
while (
|
||||
source_access(response.filename, F_OK) ||
|
||||
!(strncmp(response.filename, filename, strlen(response.filename)) == 0 &&
|
||||
((strlen(response.filename) < strlen(filename) &&
|
||||
response.filename[strlen(response.filename) - 1] == '/') ||
|
||||
(strlen(response.filename) == strlen(filename))))) {
|
||||
// if it is invalid, just ask again.
|
||||
fprintf(stderr, "Filename returned by zenty wasn't correct: %s\n",
|
||||
fprintf(stderr,
|
||||
"[ICFS] Filename returned by access dialogue wasn't correct: %s\n",
|
||||
response.filename);
|
||||
free(response.filename);
|
||||
response = ask_access(filename, proc_info);
|
||||
@@ -238,43 +264,40 @@ int interactive_access(const char *filename, struct process_info proc_info,
|
||||
real_path = real_filename(response.filename);
|
||||
free(response.filename);
|
||||
|
||||
int ret = 0;
|
||||
|
||||
if (response.decision == ALLOW) {
|
||||
fprintf(stderr,
|
||||
"Permission granted permanently to %s based on zenty response.\n",
|
||||
"[ICFS] Permission granted permanently to %s based on zenty "
|
||||
"response.\n",
|
||||
proc_info.name);
|
||||
set_perm_access(real_path, proc_info, SET_ALLOW);
|
||||
free(real_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (response.decision == ALLOW_TEMP) {
|
||||
ret = 1;
|
||||
} else if (response.decision == ALLOW_TEMP) {
|
||||
fprintf(stderr,
|
||||
"Permission granted temporarily to %s based on zenty response.\n",
|
||||
"[ICFS] Permission granted temporarily to %s based on zenty "
|
||||
"response.\n",
|
||||
proc_info.name);
|
||||
set_temp_access(real_path, proc_info, SET_ALLOW);
|
||||
free(real_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (response.decision == DENY_TEMP) {
|
||||
fprintf(stderr,
|
||||
"Permission denied temporarily to %s based on zenty response.\n",
|
||||
proc_info.name);
|
||||
ret = 1;
|
||||
} else if (response.decision == DENY_TEMP) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"[ICFS] Permission denied temporarily to %s based on zenty response.\n",
|
||||
proc_info.name);
|
||||
set_temp_access(real_path, proc_info, SET_DENY);
|
||||
free(real_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (response.decision == DENY) {
|
||||
fprintf(stderr,
|
||||
"Permission denied permanently to %s based on zenty response.\n",
|
||||
proc_info.name);
|
||||
ret = 0;
|
||||
} else if (response.decision == DENY) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"[ICFS] Permission denied permanently to %s based on zenty response.\n",
|
||||
proc_info.name);
|
||||
set_perm_access(real_path, proc_info, SET_DENY);
|
||||
free(real_path);
|
||||
return 0;
|
||||
ret = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
|
||||
free(real_path);
|
||||
// deny on unknown options.
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ void destroy_ui_socket(void);
|
||||
* 3. user descision
|
||||
*
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @param pi: The process information
|
||||
* @param opts: options (GRANT_TEMP, GRANT_PERM)
|
||||
* @return: 0 if access is denied, 1 if access is allowed
|
||||
*/
|
||||
|
@@ -2,9 +2,10 @@
|
||||
|
||||
# fake-icfs_dialogue: script that mocks the behavior of icfs_dialogue based on the ./.fake-icfs_dialogue-response file
|
||||
|
||||
ICFS_DIALOGUE_YES=0
|
||||
ICFS_DIALOGUE_NO=1
|
||||
ICFS_DIALOGUE_YES=1
|
||||
ICFS_DIALOGUE_NO=0
|
||||
ICFS_DIALOGUE_PERM=2
|
||||
ICFS_DIALOGUE_TEMP=0
|
||||
|
||||
if [[ $1 == "--set-fake-response" ]]; then
|
||||
#someone knows we are fake :)
|
||||
@@ -27,9 +28,9 @@ else
|
||||
fi
|
||||
|
||||
if [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "yes" ]]; then
|
||||
exit "$ICFS_DIALOGUE_YES"
|
||||
exit "$((ICFS_DIALOGUE_YES | ICFS_DIALOGUE_TEMP))"
|
||||
elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "no" ]]; then
|
||||
exit "$ICFS_DIALOGUE_NO"
|
||||
exit "$((ICFS_DIALOGUE_NO | ICFS_DIALOGUE_TEMP))"
|
||||
elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "yes_perm" ]]; then
|
||||
exit "$((ICFS_DIALOGUE_YES | ICFS_DIALOGUE_PERM))"
|
||||
elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "no_perm" ]]; then
|
||||
|
6
test/stress.bash
Executable file
6
test/stress.bash
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
count=$1
|
||||
for i in $(seq $count); do
|
||||
$2 ./protected/haystack/
|
||||
done
|
@@ -10,7 +10,7 @@ chmod 777 ./protected/perm777 ./protected/perm000
|
||||
echo "Free code, free world." >./protected/motto
|
||||
|
||||
mkdir protected/haystack
|
||||
for i in {1..10}; do
|
||||
for i in {1..100}; do
|
||||
touch "./protected/haystack/hay$i"
|
||||
done
|
||||
touch ./protected/haystack/needle
|
||||
@@ -22,7 +22,7 @@ make -C ./opener || (
|
||||
echo "Could not make the opener program."
|
||||
exit 1
|
||||
)
|
||||
for i in {1..10}; do
|
||||
for i in {1..12}; do
|
||||
cp ./opener/opener "./openers/opener$i"
|
||||
ln --symbolic "$(realpath "./openers/opener$i")" "./openers/symlinked_opener$i"
|
||||
done
|
||||
@@ -43,16 +43,24 @@ if [[ $1 == "--setuid" ]]; then
|
||||
echo "Valgrind will not be used due to setuid compatibility issues."
|
||||
../build/icfs -o default_permissions ./protected ./.pt.db &
|
||||
sleep 1
|
||||
elif [[ $1 == "--performance" ]]; then
|
||||
echo "Database protection will not be tested due to the lack of setuid capabilites."
|
||||
echo "To test it, run this script with '--setuid'."
|
||||
echo "valgrind won't be used to make performance measurements more accurate."
|
||||
../build/icfs -o default_permissions ./protected ./.pt.db &
|
||||
sleep 5
|
||||
else
|
||||
echo "Database protection will not be tested due to the lack of setuid capabilites."
|
||||
echo "To test it, run this script with '--setuid'."
|
||||
#valgrind --leak-check=full -s ../build/icfs -o default_permissions -o debug ./protected ./.pt.db 2>&1 | grep "==\|zenity\|Permission\|column\|callback" &
|
||||
valgrind --leak-check=full -s ../build/icfs -o default_permissions ./protected ./.pt.db &
|
||||
#valgrind --leak-check=full -s ../build/icfs -o default_permissions -o debug ./protected ./.pt.db 2>&1 | grep "==\|zenity\|Permission\|column\|callback\|SQLite" &
|
||||
valgrind --leak-check=full --show-leak-kinds=all -s ../build/icfs -o default_permissions ./protected ./.pt.db &
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
#valgrind -s ../build/icfs -o default_permissions ./protected &
|
||||
|
||||
echo "[ICFS-TEST]: You may see memory errors from valgrind at this stage. This is normal - memory isn't lost, the libfuse just forked the process."
|
||||
|
||||
# WARN: please don't use `>` or `>>` operators. They force **this script** to open the file, **not the program you are trying to run**. This is probably not what you mean when you want to test a specific program's access.
|
||||
# WARN: avoid using touch, since it generates errors because setting times is not implemented in icfs **yet**.
|
||||
|
||||
@@ -205,9 +213,36 @@ else
|
||||
echo "[ICFS-TEST]: permanent permissions database access was not tested due to the lack of seuid bit setting capabilites. To test this, run the script with '--setuid' flag"
|
||||
fi
|
||||
|
||||
RUNS_NUM=500
|
||||
|
||||
if [[ $1 == '--performance' ]]; then
|
||||
|
||||
#warmup
|
||||
icfs_dialogue --set-fake-response yes
|
||||
parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener10"
|
||||
|
||||
icfs_dialogue --set-fake-response yes
|
||||
echo "[ICFS-TEST]: temp permissions"
|
||||
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener7"
|
||||
|
||||
icfs_dialogue --set-fake-response yes_perm
|
||||
echo "[ICFS-TEST]: perm permissions"
|
||||
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener8"
|
||||
|
||||
fi
|
||||
|
||||
# unmount
|
||||
|
||||
sleep 0.5
|
||||
#lsof +f -- $(realpath ./protected)
|
||||
umount "$(realpath ./protected)"
|
||||
sleep 0.5
|
||||
sleep 3
|
||||
|
||||
if [[ $1 == '--performance' ]]; then
|
||||
|
||||
#warmup
|
||||
parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener9"
|
||||
|
||||
echo "[ICFS-TEST]: bare filesystem"
|
||||
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener9"
|
||||
fi
|
||||
|
Reference in New Issue
Block a user