Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
6fd6907121 | |||
1bc25af6f1 |
10
.gitignore
vendored
10
.gitignore
vendored
@ -2,13 +2,7 @@ build/*
|
||||
.clang-tidy
|
||||
.cache
|
||||
test/protected/*
|
||||
test/.pt.db
|
||||
compile_commands.json
|
||||
test/perf*
|
||||
test/callgraph*
|
||||
test/openers
|
||||
test/opener/opener
|
||||
test/opener/opener.o
|
||||
test/.*
|
||||
*compile_commands.json
|
||||
src/gui/ui/*
|
||||
src/gui/*.o
|
||||
src/gui/icfs_dialogue
|
||||
|
25
CONTENTS.md
25
CONTENTS.md
@ -1,25 +0,0 @@
|
||||
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.
|
80
Makefile
80
Makefile
@ -2,19 +2,9 @@ SHELL=/bin/bash
|
||||
|
||||
# configurable options
|
||||
|
||||
ifndef ($(SOURCES_DIR))
|
||||
SOURCES_DIR := ./src
|
||||
endif
|
||||
|
||||
ifndef ($(TESTS_DIR))
|
||||
TESTS_DIR := ./tests
|
||||
endif
|
||||
|
||||
ifndef ($(BUILD_DIR))
|
||||
BUILD_DIR := ./build
|
||||
endif
|
||||
|
||||
|
||||
SOURCES_DIR := ./src
|
||||
TESTS_DIR := ./tests
|
||||
BUILD_DIR := ./build
|
||||
|
||||
CC := gcc
|
||||
CXX := g++
|
||||
@ -31,14 +21,14 @@ endif
|
||||
|
||||
# set up cflags and libs
|
||||
|
||||
CFLAGS := -D_FILE_OFFSET_BITS=64
|
||||
CFLAGS := -D_FILE_OFFSET_BITS=64 -g
|
||||
LDFLAGS :=
|
||||
|
||||
CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES))
|
||||
LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES))
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
CFLAGS += -Og -pedantic -g -Wall -Wextra -Wcast-align \
|
||||
CFLAGS += -O0 -pedantic -Wall -Wextra -Wcast-align \
|
||||
-Wcast-qual -Wdisabled-optimization -Wformat=2 \
|
||||
-Winit-self -Wlogical-op -Wmissing-declarations \
|
||||
-Wmissing-include-dirs -Wredundant-decls -Wshadow \
|
||||
@ -59,78 +49,40 @@ ifeq ($(TEST), 1)
|
||||
TARGETS += icfs_test
|
||||
endif
|
||||
|
||||
ifneq ($(DIALOGUE), 0)
|
||||
TARGETS += $(BUILD_DIR)/icfs_dialogue
|
||||
endif
|
||||
|
||||
# build!
|
||||
|
||||
default: $(TARGETS)
|
||||
|
||||
.PHONY: clean icfs_test clean-icfs clean-icfs_dialogue install uninstall
|
||||
.PHONY: clean
|
||||
|
||||
$(BUILD_DIR):
|
||||
if [[ ! -d "$(BUILD_DIR)" ]]; then mkdir $(BUILD_DIR); fi
|
||||
|
||||
$(BUILD_DIR)/icfs_dialogue:
|
||||
make -C $(SOURCES_DIR)/gui TEST=$(TEST) DEBUG=$(DEBUG) SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_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
|
||||
$(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
|
||||
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs
|
||||
|
||||
icfs_test: $(BUILD_DIR)/icfs
|
||||
cd ./test && ./test.bash
|
||||
|
||||
$(BUILD_DIR)/test_access_control.o: $(TESTS_DIR)/test_access_control.c $(BUILD_DIR)
|
||||
$(BUILD_DIR)/test_access_control.o: $(TESTS_DIR)/test_access_control.c
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(BUILD_DIR)/main.o: $(SOURCES_DIR)/main.c $(BUILD_DIR)
|
||||
$(BUILD_DIR)/main.o: $(SOURCES_DIR)/main.c
|
||||
$(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)
|
||||
$(BUILD_DIR)/fuse_operations.o: $(SOURCES_DIR)/fuse_operations.c $(SOURCES_DIR)/fuse_operations.h
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(BUILD_DIR)/sourcefs.o: $(SOURCES_DIR)/sourcefs.c $(SOURCES_DIR)/sourcefs.h $(SOURCES_DIR)/real_filename.h $(BUILD_DIR)
|
||||
$(BUILD_DIR)/sourcefs.o: $(SOURCES_DIR)/sourcefs.c $(SOURCES_DIR)/sourcefs.h
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h $(BUILD_DIR)
|
||||
$(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h
|
||||
$(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)
|
||||
$(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $(SOURCES_DIR)/temp_permissions_table.h
|
||||
$(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)
|
||||
$(BUILD_DIR)/perm_permissions_table.o: $(SOURCES_DIR)/perm_permissions_table.c $(SOURCES_DIR)/perm_permissions_table.h
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
|
||||
|
||||
$(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
|
||||
|
||||
ifneq ($(DIALOGUE), 0)
|
||||
CLEAN_TARGETS += clean-icfs_dialogue
|
||||
endif
|
||||
|
||||
clean: $(CLEAN_TARGETS)
|
||||
|
||||
clean-icfs:
|
||||
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/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 4755 /usr/bin/icfs && sudo chmod 755 /usr/bin/icfs_dialogue
|
||||
@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
|
||||
|
||||
clean:
|
||||
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs*
|
||||
|
65
README.md
65
README.md
@ -1,9 +1,5 @@
|
||||
|
||||
# ICFS -- Interactively Controlled File System
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you need the version that correponds to the thesis attachment, go [here](https://git.umbrasolis.de/fedir/ICFS/src/commit/2f4f1a0a569704b770f50b3e7cf39c09b9b8381a). This version contains corrections of errors that the opponent brought to my attention.
|
||||
|
||||
## Motivation
|
||||
|
||||
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 file system layer which delegates the decision to allow or deny access to a file system object by a specific process to the user.
|
||||
@ -19,67 +15,20 @@ Traditional access control mechanisms in operating systems allow the same level
|
||||
- Install dependencies
|
||||
- libfuse3
|
||||
- Debian: `sudo apt install fuse3 libfuse3-dev`
|
||||
- SQLite3
|
||||
- Debian: `sudo apt install libsqlite3-dev`
|
||||
- GTK4, libadwaita
|
||||
- Debian: `sudo apt install libgtk-4-dev libadwaita-1-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`
|
||||
- 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.
|
||||
- Use `make DEBUG=1` for testing.
|
||||
- Resulting binaries should appear in the `build` directory.
|
||||
|
||||
## Installation
|
||||
|
||||
- `make install`
|
||||
- Uninstall with `make uninstall`
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Usage: icfs <FUSE arguments> [target directory] [path to the permanent permissions database] <ICFS arguments>
|
||||
--no-grant-on-create - do not give any access permissions on file creation(incompatible with --perm-on-create)
|
||||
--perm-on-create - automatically give permanent access permission to files a process creates (incompatible with --no-grant-on-create)
|
||||
```
|
||||
`icfs <FUSE arguments> [target directory]`
|
||||
|
||||
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.
|
||||
|
||||
#### Running tests
|
||||
|
||||
ICFS includes a testing script in the `test` directory.
|
||||
|
||||
You can run it **from `test` directory** by running:
|
||||
|
||||
```
|
||||
./test.bash
|
||||
```
|
||||
|
||||
All testing artifacts will be available in the appropriate folders after run. To test setuid capabilities too (**from `test` directory!!!**):
|
||||
|
||||
```
|
||||
./test.bash --setuid
|
||||
```
|
||||
|
||||
You can also test performance by adding `--performance` (**from `test` directory!!!**):
|
||||
|
||||
```
|
||||
./test.bash --performance
|
||||
```
|
||||
|
||||
***Important:*** **flags cannot be combined together (e.g. you can't add `--performance` and `--setuid`)**
|
||||
The filesystem will be mounted over the target directory, and ask user permission every time a file in that directory is opened.
|
||||
|
||||
## Docs
|
||||
|
||||
@ -89,6 +38,6 @@ You can also test performance by adding `--performance` (**from `test` directory
|
||||
|
||||
## Credit
|
||||
|
||||
*Student:* Fedir Kovalov
|
||||
_Student:_ Fedir Kovalov
|
||||
|
||||
*Supervisor:* RNDr. Jaroslav Janáček, PhD.
|
||||
_Supervisor:_ RNDr. Jaroslav Janáček, PhD.
|
||||
|
66
docs/bc-thesis-idea.md
Normal file
66
docs/bc-thesis-idea.md
Normal file
@ -0,0 +1,66 @@
|
||||
# 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.
|
78
docs/bc-thesis-problems.md
Normal file
78
docs/bc-thesis-problems.md
Normal file
@ -0,0 +1,78 @@
|
||||
|
||||
# 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.
|
33
docs/bc-thesis-specs.md
Normal file
33
docs/bc-thesis-specs.md
Normal file
@ -0,0 +1,33 @@
|
||||
# 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,14 +0,0 @@
|
||||
/*
|
||||
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
|
||||
|
||||
typedef enum { DENY, ALLOW, ALLOW_TEMP, DENY_TEMP, NDEF } access_t;
|
||||
|
||||
#endif // !ACCESS_T_H
|
@ -11,10 +11,6 @@
|
||||
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,22 +37,65 @@
|
||||
#include <sys/file.h> /* flock(2) */
|
||||
|
||||
#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;
|
||||
const char *get_process_name_by_pid(const int pid) {
|
||||
char path[1024];
|
||||
sprintf(path, "/proc/%d/exe", pid);
|
||||
|
||||
#define HAVE_UTIMENSAT
|
||||
char *name = realpath(path, NULL);
|
||||
if (name == NULL) {
|
||||
fprintf(stderr, "Could not get process name by pid %d", pid);
|
||||
perror("");
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the default permission granted by file creation.
|
||||
*
|
||||
* @param val: the default permission to grant. Should be one of GRANT_TEMP,
|
||||
* GRANT_TEM and 0. When set to 0, grants no permissions.
|
||||
*/
|
||||
void set_auto_create_perm(int val) { auto_create_perm = val; }
|
||||
/*
|
||||
size_t namelen = 32;
|
||||
ssize_t readret = 0;
|
||||
char *name = NULL;
|
||||
while (namelen >= (size_t)readret && readret > 0) {
|
||||
namelen *= 2;
|
||||
name = calloc(namelen, sizeof(char));
|
||||
if (name == NULL) {
|
||||
free(path);
|
||||
fprintf(stderr, "Could not get get process name by pid %d", pid);
|
||||
perror("");
|
||||
return NULL;
|
||||
}
|
||||
readret = readlink(path, name, namelen);
|
||||
if (readret < 0) {
|
||||
free(name);
|
||||
free(path);
|
||||
fprintf(stderr, "Couldn't get process name by pid %d", pid);
|
||||
perror("");
|
||||
return NULL;
|
||||
}
|
||||
if (namelen >= (size_t)readret) {
|
||||
free(name);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return name;
|
||||
|
||||
/*
|
||||
FILE *file = fopen(path, "r");
|
||||
if (file) {
|
||||
size_t size = 0;
|
||||
size = fread(path, sizeof(char), 1024, file);
|
||||
if (size > 0) {
|
||||
if ('\n' == path[size - 1]) {
|
||||
path[size - 1] = '\0';
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// TODO: move this somewhere else
|
||||
const char *real_filename(const char *filename) { return filename; }
|
||||
|
||||
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
|
||||
(void)conn;
|
||||
@ -68,9 +107,7 @@ 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
|
||||
@ -83,8 +120,6 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
|
||||
cfg->attr_timeout = 0;
|
||||
cfg->negative_timeout = 0;
|
||||
fprintf(stderr, "%d\n", getpid());
|
||||
assert(get_mountpoint() != NULL);
|
||||
init_garbage_collector();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@ -100,7 +135,7 @@ static int xmp_getattr(const char *path, struct stat *stbuf,
|
||||
else
|
||||
res = source_stat(path, stbuf);
|
||||
if (res == -1) {
|
||||
perror("[ICFS] Stat failed");
|
||||
perror("Stat failed");
|
||||
return -errno;
|
||||
}
|
||||
|
||||
@ -112,11 +147,7 @@ static int xmp_access(const char *path, int mask) {
|
||||
|
||||
// if mask is F_OK, then we don't need to check the permissions
|
||||
// (is that possible?)
|
||||
//
|
||||
// EDIT: now lie to the program by not telling it whether it can actually
|
||||
// access the file.
|
||||
|
||||
/*
|
||||
if (mask != F_OK) {
|
||||
struct process_info proc_info;
|
||||
struct fuse_context *context = fuse_get_context();
|
||||
@ -126,14 +157,13 @@ static int xmp_access(const char *path, int mask) {
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
|
||||
if (!interactive_access(path, proc_info, 0)) {
|
||||
if (!interactive_access(real_filename(path), proc_info, 0)) {
|
||||
free((void *)proc_info.name);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
free((void *)proc_info.name);
|
||||
}
|
||||
*/
|
||||
|
||||
res = source_access(path, mask);
|
||||
|
||||
@ -169,7 +199,7 @@ static int xmp_opendir(const char *path, struct fuse_file_info *fi) {
|
||||
|
||||
d->dp = source_opendir(path);
|
||||
if (d->dp == NULL) {
|
||||
perror("[ICFS] Opendir failed");
|
||||
perror("Opendir failed");
|
||||
res = -errno;
|
||||
free(d);
|
||||
return res;
|
||||
@ -288,11 +318,12 @@ 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 = get_process_info(fc->pid);
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
|
||||
if (!interactive_access(path, pi, 0)) {
|
||||
if (!interactive_access(real_filename(path), pi, 0)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
@ -335,18 +366,20 @@ 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 = get_process_info(fc->pid);
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
|
||||
if (!interactive_access(from, pi, 0)) {
|
||||
if (!interactive_access(real_filename(from), pi, 0)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
// the "to" file may exist and the process needs to get persmission to modify
|
||||
// it
|
||||
if (source_access(to, F_OK) == 0 && !interactive_access(to, pi, 0)) {
|
||||
if (source_access(to, F_OK) == 0 &&
|
||||
!interactive_access(real_filename(to), pi, 0)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
@ -365,10 +398,11 @@ static int xmp_link(const char *from, const char *to) {
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi = get_process_info(fc->pid);
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
if (!interactive_access(from, pi, 0)) {
|
||||
if (!interactive_access(real_filename(from), pi, 0)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
@ -389,10 +423,11 @@ 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 = get_process_info(fc->pid);
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
if (!interactive_access(path, pi, 0)) {
|
||||
if (!interactive_access(real_filename(path), pi, 0)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
@ -410,8 +445,8 @@ static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This filesystem is not designed for multiuser operation (with
|
||||
* allow_other option) so there is little point in having chown implemnted
|
||||
* This filesystem is not designed for multiuser operation (e.g. with
|
||||
* allow_other) so there is little point in having chown implemnted
|
||||
*/
|
||||
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
|
||||
struct fuse_file_info *fi) {
|
||||
@ -419,10 +454,11 @@ 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 = get_process_info(fc->pid);
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
if (!interactive_access(path, pi, 0)) {
|
||||
if (!interactive_access(real_filename(path), pi, 0)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
@ -458,21 +494,12 @@ static int xmp_truncate(const char *path, off_t size,
|
||||
static int xmp_utimens(const char *path, const struct timespec ts[2],
|
||||
struct fuse_file_info *fi) {
|
||||
int res;
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi = get_process_info(fc->pid);
|
||||
|
||||
if (!interactive_access(path, pi, 0)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
/* don't use utime/utimes since they follow symlinks */
|
||||
if (fi)
|
||||
res = futimens(fi->fh, ts);
|
||||
else
|
||||
res = source_utimens(path, ts, AT_SYMLINK_NOFOLLOW);
|
||||
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
|
||||
if (res == -1)
|
||||
return -errno;
|
||||
|
||||
@ -486,13 +513,14 @@ static int xmp_create(const char *path, mode_t mode,
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi = get_process_info(fc->pid);
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
|
||||
if (auto_create_perm != 0) {
|
||||
if (!interactive_access(path, pi, auto_create_perm)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
|
||||
if (!interactive_access(real_filename(path), pi, GRANT_PERM)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
free(pi.name);
|
||||
@ -510,10 +538,11 @@ static int xmp_open(const char *path, struct fuse_file_info *fi) {
|
||||
struct process_info pi;
|
||||
struct fuse_context *fc = fuse_get_context();
|
||||
|
||||
pi = get_process_info(fc->pid);
|
||||
pi.PID = fc->pid;
|
||||
pi.name = get_process_name_by_pid(pi.PID);
|
||||
|
||||
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
|
||||
if (!interactive_access(path, pi, 0)) {
|
||||
if (!interactive_access(real_filename(path), pi, 0)) {
|
||||
free(pi.name);
|
||||
return -EACCES;
|
||||
}
|
||||
@ -772,7 +801,7 @@ static const struct fuse_operations xmp_oper = {
|
||||
.chown = xmp_chown,
|
||||
.truncate = xmp_truncate,
|
||||
#ifdef HAVE_UTIMENSAT
|
||||
.utimens = xmp_utimens,
|
||||
// .utimens = xmp_utimens,
|
||||
#endif
|
||||
.create = xmp_create,
|
||||
.open = xmp_open,
|
||||
|
@ -15,12 +15,4 @@
|
||||
|
||||
const struct fuse_operations *get_fuse_operations();
|
||||
|
||||
/*
|
||||
* Sets the default permission granted by file creation.
|
||||
*
|
||||
* @param val: the default permission to grant. Should be one of GRANT_TEMP,
|
||||
* GRANT_TEM and 0. When set to 0, grants no permissions.
|
||||
*/
|
||||
void set_auto_create_perm(int val);
|
||||
|
||||
#endif
|
||||
|
@ -1,77 +0,0 @@
|
||||
SHELL=/bin/bash
|
||||
|
||||
# configurable options
|
||||
|
||||
ifndef ($(SOURCES_DIR))
|
||||
SOURCES_DIR := .
|
||||
endif
|
||||
|
||||
ifndef ($(TESTS_DIR))
|
||||
TESTS_DIR := .
|
||||
endif
|
||||
|
||||
ifndef ($(BUILD_DIR))
|
||||
BUILD_DIR := .
|
||||
endif
|
||||
|
||||
CC := gcc
|
||||
CXX := g++
|
||||
|
||||
|
||||
# dependencies
|
||||
|
||||
PACKAGE_NAMES := gtk4 libadwaita-1
|
||||
|
||||
ifeq ($(TEST), 1)
|
||||
# PACKAGE_NAMES += check # TODO: use check?
|
||||
endif
|
||||
|
||||
|
||||
# set up cflags and libs
|
||||
|
||||
CFLAGS := -D_FILE_OFFSET_BITS=64
|
||||
LDFLAGS :=
|
||||
|
||||
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 \
|
||||
-Wcast-qual -Wdisabled-optimization -Wformat=2 \
|
||||
-Winit-self -Wlogical-op -Wmissing-declarations \
|
||||
-Wmissing-include-dirs -Wredundant-decls -Wshadow \
|
||||
-Wsign-conversion -Wstrict-overflow=5 \
|
||||
-Wswitch-default -Wundef -Wno-unused
|
||||
LDFLAGS +=
|
||||
else
|
||||
CFLAGS += -O3
|
||||
LDFLAGS +=
|
||||
endif
|
||||
|
||||
|
||||
# set up targets
|
||||
|
||||
TARGETS := $(BUILD_DIR)/icfs_dialogue
|
||||
|
||||
ifeq ($(TEST), 1)
|
||||
TARGETS += icfs_dialogue_test
|
||||
endif
|
||||
|
||||
|
||||
# build!
|
||||
|
||||
default: $(TARGETS)
|
||||
|
||||
.PHONY: clean icfs_dialogue_test
|
||||
|
||||
icfs_dialogue_test: $(BUILD_DIR)/icfs_dialogue
|
||||
$(BUILD_DIR)/icfs_dialogue 666 cat /home/fedir /Downloads
|
||||
|
||||
$(BUILD_DIR)/icfs_dialogue: $(BUILD_DIR)/icfs_dialogue.o
|
||||
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs_dialogue
|
||||
|
||||
$(BUILD_DIR)/icfs_dialogue.o: $(SOURCES_DIR)/icfs_dialogue.c
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $(BUILD_DIR)/icfs_dialogue.o
|
||||
|
||||
clean:
|
||||
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs_dialogue
|
@ -1,169 +0,0 @@
|
||||
#include "gio/gio.h"
|
||||
#include "glib-object.h"
|
||||
#include "glib.h"
|
||||
#include <adwaita.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define YES 1
|
||||
#define NO 0
|
||||
#define PERM 2
|
||||
#define TEMP 0
|
||||
|
||||
int exit_code = 0;
|
||||
gboolean is_permanent = false;
|
||||
GtkEntryBuffer *entry_buffer = NULL;
|
||||
GtkWidget *checkbox = NULL;
|
||||
|
||||
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 | TEMP;
|
||||
gtk_window_close(window);
|
||||
}
|
||||
|
||||
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 | TEMP;
|
||||
gtk_window_close(window);
|
||||
}
|
||||
|
||||
static void on_check_button_toggled(GtkToggleButton *button,
|
||||
gpointer user_data) {
|
||||
gboolean active = gtk_toggle_button_get_active(button);
|
||||
}
|
||||
|
||||
static void on_activate(GtkApplication *app, gpointer user_data) {
|
||||
// Create the main window
|
||||
AdwWindow *window = ADW_WINDOW(adw_window_new());
|
||||
gtk_window_set_application(GTK_WINDOW(window), app);
|
||||
gtk_window_set_title(GTK_WINDOW(window), "icfs");
|
||||
// gtk_window_set_default_size(GTK_WINDOW(window), 300, 150);
|
||||
|
||||
AdwStatusPage *content = ADW_STATUS_PAGE(adw_status_page_new());
|
||||
adw_status_page_set_title(content, "Allow access?");
|
||||
|
||||
char *description = NULL;
|
||||
asprintf(
|
||||
&description,
|
||||
"Allow process <tt>%s</tt> with PID <tt>%s</tt> to access <tt>%s</tt>",
|
||||
g_object_get_data(G_OBJECT(app), "accessing_name"),
|
||||
g_object_get_data(G_OBJECT(app), "accessing_pid"),
|
||||
g_object_get_data(G_OBJECT(app), "access_dir"));
|
||||
adw_status_page_set_description(content, description);
|
||||
free(description);
|
||||
|
||||
entry_buffer = gtk_entry_buffer_new(
|
||||
g_object_get_data(G_OBJECT(app), "access_dir"),
|
||||
strlen(g_object_get_data(G_OBJECT(app), "access_dir")));
|
||||
|
||||
GtkWidget *entry = gtk_entry_new();
|
||||
gtk_entry_set_buffer(GTK_ENTRY(entry), entry_buffer);
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(entry), "Enter filename");
|
||||
gtk_widget_set_hexpand(entry, TRUE);
|
||||
|
||||
// Create a prefix label and box
|
||||
GtkWidget *entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
GtkWidget *prefix_label =
|
||||
gtk_label_new(g_object_get_data(G_OBJECT(app), "root_folder"));
|
||||
gtk_box_append(GTK_BOX(entry_box), prefix_label);
|
||||
gtk_box_append(GTK_BOX(entry_box), entry);
|
||||
|
||||
checkbox = gtk_check_button_new_with_label("Permanent");
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(checkbox), false);
|
||||
// gtk_widget_set_halign(checkbox, GTK_ALIGN_CENTER);
|
||||
|
||||
GtkWidget *yes_button = gtk_button_new_with_label("Yes");
|
||||
gtk_widget_set_hexpand(yes_button, TRUE);
|
||||
g_signal_connect_swapped(yes_button, "clicked", G_CALLBACK(positive_response),
|
||||
window);
|
||||
|
||||
GtkWidget *no_button = gtk_button_new_with_label("No");
|
||||
gtk_widget_set_hexpand(no_button, TRUE);
|
||||
g_signal_connect_swapped(no_button, "clicked", G_CALLBACK(negative_response),
|
||||
window);
|
||||
|
||||
GtkWidget *button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
||||
gtk_box_append(GTK_BOX(button_box), yes_button);
|
||||
gtk_box_append(GTK_BOX(button_box), no_button);
|
||||
gtk_widget_set_halign(button_box, GTK_ALIGN_FILL);
|
||||
|
||||
// Combine everything in a box
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||
gtk_box_append(GTK_BOX(box), GTK_WIDGET(content));
|
||||
gtk_box_append(GTK_BOX(box), entry_box);
|
||||
gtk_box_append(GTK_BOX(box), checkbox);
|
||||
gtk_box_append(GTK_BOX(box), button_box);
|
||||
gtk_widget_set_margin_top(GTK_WIDGET(box), 12);
|
||||
gtk_widget_set_margin_bottom(GTK_WIDGET(box), 12);
|
||||
gtk_widget_set_margin_start(GTK_WIDGET(box), 12);
|
||||
gtk_widget_set_margin_end(GTK_WIDGET(box), 12);
|
||||
|
||||
// g_signal_connect(window, "response", G_CALLBACK(gtk_window_close), window);
|
||||
|
||||
// Show the dialog
|
||||
adw_window_set_content(window, box);
|
||||
gtk_window_present(GTK_WINDOW(window));
|
||||
}
|
||||
|
||||
static int on_command_line(GApplication *app, GApplicationCommandLine *cmdline,
|
||||
gpointer user_data) {
|
||||
gchar **argv;
|
||||
gint argc;
|
||||
|
||||
argv = g_application_command_line_get_arguments(cmdline, &argc);
|
||||
|
||||
// Handle your arguments here
|
||||
if (argc >= 5) {
|
||||
fprintf(stderr, "%s\n", argv[1]);
|
||||
g_object_set_data_full(G_OBJECT(app), "accessing_pid", g_strdup(argv[1]),
|
||||
g_free);
|
||||
g_object_set_data_full(G_OBJECT(app), "accessing_name", g_strdup(argv[2]),
|
||||
g_free);
|
||||
g_object_set_data_full(G_OBJECT(app), "root_folder", g_strdup(argv[3]),
|
||||
g_free);
|
||||
g_object_set_data_full(G_OBJECT(app), "access_dir", g_strdup(argv[4]),
|
||||
g_free);
|
||||
}
|
||||
|
||||
g_strfreev(argv);
|
||||
|
||||
// Activate the application
|
||||
g_application_activate(app);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
if (argc == 2 && strcmp(argv[1], "--version") == 0) {
|
||||
fprintf(stdout, "icfs_dialogue 1.0.0");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc != 5) {
|
||||
fprintf(stdout, "Usage: icfs_dialogue [accessing pid] [accessing name] "
|
||||
"[root folder] [access dir]");
|
||||
return 255;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
g_signal_connect(app, "command-line", G_CALLBACK(on_command_line), NULL);
|
||||
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
|
||||
|
||||
// Run the application
|
||||
int status = g_application_run(G_APPLICATION(app), argc, argv);
|
||||
g_object_unref(app);
|
||||
|
||||
return (status == 0) ? exit_code : status;
|
||||
}
|
38
src/main.c
38
src/main.c
@ -10,7 +10,6 @@
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#define FUSE_USE_VERSION 31
|
||||
|
||||
#define _GNU_SOURCE
|
||||
@ -30,28 +29,7 @@ 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] <ICFS "
|
||||
"arguments>\n\t--no-grant-on-create - do not give any "
|
||||
"access permissions on file creation"
|
||||
"(incompatible with --perm-on-create)\n\t--perm-on-create "
|
||||
"- automatically give permanent access permission to files "
|
||||
"a process creates "
|
||||
"(incompatible with --no-grant-on-create)\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if ((0 == strcmp(argv[argc - 1], "--no-grant-on-create") &&
|
||||
0 == strcmp(argv[argc - 2], "--temp-on-create")) ||
|
||||
(0 == strcmp(argv[argc - 2], "--no-grant-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-grant-on-create - do not give any "
|
||||
"access permissions on file creation"
|
||||
"(incompatible with --perm-on-create)\n\t--perm-on-create "
|
||||
"- automatically give permanent access permission to files "
|
||||
"a process creates "
|
||||
"(incompatible with --no-grant-on-create)\n");
|
||||
"the permanent permissions database\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -59,20 +37,11 @@ int main(int argc, char *argv[]) {
|
||||
// permissions than it's caller reqested
|
||||
umask(0);
|
||||
|
||||
if (0 == strcmp(argv[argc - 1], "--no-grant-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, "[ICFS] Could not initalize ui-socket.\n");
|
||||
fprintf(stderr, "Could not initalize ui-socket.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@ -80,14 +49,13 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
ret = source_init(mountpoint);
|
||||
if (ret != 0) {
|
||||
perror("[ICFS] source_init");
|
||||
perror("source_init");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ret = fuse_main(argc - 1, argv, get_fuse_operations(), NULL);
|
||||
|
||||
free((void *)mountpoint);
|
||||
source_destroy();
|
||||
destroy_ui_socket();
|
||||
return ret;
|
||||
}
|
||||
|
@ -7,10 +7,7 @@
|
||||
*/
|
||||
|
||||
#include "perm_permissions_table.h"
|
||||
#include "access_t.h"
|
||||
#include "proc_operations.h"
|
||||
#include "process_info.h"
|
||||
#include "set_mode_t.h"
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <sqlite3.h>
|
||||
@ -23,279 +20,284 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Global pointer to the SQLite database storing permanent permissions
|
||||
sqlite3 *perm_database = NULL;
|
||||
const char *const table_name = "permissions";
|
||||
// Each row represents a permission to access a specific file for an executable
|
||||
const int column_count = 3;
|
||||
// Column names in the permissions table
|
||||
const char *const schema[] = {"executable", "filename", "mode"};
|
||||
// Expected SQL data types for each column
|
||||
const char *const types[] = {"TEXT", "TEXT", "INTEGER"};
|
||||
uid_t ruid, euid, current_uid;
|
||||
// one row corresponds to a permission to access one file for one executable
|
||||
const int column_count = 2;
|
||||
const char *const schema[] = {"executable", "filename"};
|
||||
const char *const types[] = {"TEXT", "TEXT"};
|
||||
uid_t ruid, euid, current_pid;
|
||||
sqlite3_stmt *perm_check_statement = NULL;
|
||||
pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void set_db_fsuid() {
|
||||
pthread_mutex_lock(&uid_switch);
|
||||
if (current_pid == ruid)
|
||||
return;
|
||||
|
||||
int status = -1;
|
||||
|
||||
status = setfsuid(ruid);
|
||||
if (status < 0) {
|
||||
fprintf(stderr, "Couldn't set uid to %d.\n", ruid);
|
||||
exit(status);
|
||||
}
|
||||
pthread_mutex_unlock(&uid_switch);
|
||||
}
|
||||
|
||||
void set_real_fsuid() {
|
||||
pthread_mutex_lock(&uid_switch);
|
||||
if (current_pid == ruid)
|
||||
return;
|
||||
|
||||
int status = -1;
|
||||
|
||||
status = setfsuid(ruid);
|
||||
if (status < 0) {
|
||||
fprintf(stderr, "Couldn't set uid to %d.\n", euid);
|
||||
exit(status);
|
||||
}
|
||||
pthread_mutex_unlock(&uid_switch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to validate the database schema matches expectations.
|
||||
* Verifies column order, names, and types during schema checks.
|
||||
*
|
||||
* @param unused: Ignored context pointer
|
||||
* @param argc: Number of columns returned (should be 3)
|
||||
* @param argv: Column data: index 0=name, 1=type, 2=notnull flag
|
||||
* @param colname: Column names (unused)
|
||||
* @return 0 on success, 1 if schema mismatch
|
||||
*/
|
||||
static int check_table_col_schema(void *notused, int argc, char **argv,
|
||||
char **colname) {
|
||||
(void)notused;
|
||||
(void)colname;
|
||||
if (argc < 3) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] Unexpected amount of arguments given to the callback.\n");
|
||||
fprintf(stderr, "Unexpected amount of arguments given to the callback.\n");
|
||||
return 1;
|
||||
}
|
||||
int column_num = atoi(argv[0]);
|
||||
if (column_num >= column_count) {
|
||||
fprintf(stderr, "[ICFS] Table contains unexpected amount of columns.\n");
|
||||
fprintf(stderr, "Table contains more columns than expected.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(schema[column_num], argv[1]) == 0 &&
|
||||
strcmp(types[column_num], argv[2]) == 0) {
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "[ICFS] Column %d does not conform to the schema.\n",
|
||||
column_num);
|
||||
fprintf(stderr, "Column %d does not conform to the schema.\n", column_num);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the permissions table schema in the database.
|
||||
* Called when the table doesn't exist yet.
|
||||
*
|
||||
* @return 0 on success, 1 on failure
|
||||
*/
|
||||
int create_database_schema(void) {
|
||||
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);";
|
||||
static int set_flag(void *flag, int argc, char **argv, char **colname) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
(void)colname;
|
||||
*(int *)flag = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_database_schema() {
|
||||
fprintf(stderr, "Creating table 'permissions'.\n");
|
||||
const char *create_query = "CREATE TABLE permissions(executable TEXT NOT "
|
||||
"NULL, filename TEXT NOT NULL);";
|
||||
char *err = NULL;
|
||||
int ret = sqlite3_exec(perm_database, create_query, NULL, NULL, &err);
|
||||
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
|
||||
fprintf(stderr, "sqlite3 error: %s\n", err);
|
||||
sqlite3_free(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[ICFS] Database created successfully\n");
|
||||
fprintf(stderr, "Database created successfully\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the database schema matches expected structure.
|
||||
* Validates table existence and column definitions.
|
||||
* Ensures that the database schema is correct.
|
||||
*
|
||||
* @return 0 if schema is valid, 1 if validation failed or repair failed
|
||||
* @return: 0 if the schema is correct, 1 if the schema could not be corrected.
|
||||
*/
|
||||
int ensure_database_schema(void) {
|
||||
int ensure_database_schema() {
|
||||
// Check for the table.
|
||||
int result = sqlite3_table_column_metadata(
|
||||
perm_database, NULL, table_name, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
if (result == SQLITE_ERROR) {
|
||||
fprintf(stderr, "[ICFS] Table '%s' does not exist.\n", table_name);
|
||||
fprintf(stderr, "Table '%s' does not exist.\n", table_name);
|
||||
if (create_database_schema()) {
|
||||
fprintf(stderr, "[ICFS] Table could not be created.\n");
|
||||
fprintf(stderr, "Table could not be created.\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else if (result != SQLITE_OK) {
|
||||
fprintf(stderr, "[ICFS] Database metadata could not be retrieved.\n");
|
||||
fprintf(stderr, "Database metadata could not be retrieved.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Verify column definitions
|
||||
const char *pragma = "PRAGMA table_info(permissions);";
|
||||
char *err = NULL;
|
||||
int ret =
|
||||
sqlite3_exec(perm_database, pragma, check_table_col_schema, NULL, &err);
|
||||
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
|
||||
fprintf(stderr, "sqlite3 error: %s\n", err);
|
||||
sqlite3_free(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[ICFS] Schema is correct.\n");
|
||||
fprintf(stderr, "Schema is correct.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int prepare_sql_queries() {
|
||||
const char *query_template =
|
||||
"SELECT * FROM %s WHERE executable = ? AND filename = ?;";
|
||||
char *query_string = NULL;
|
||||
int query_len = snprintf(NULL, 0, query_template, table_name) + 1;
|
||||
|
||||
if (query_len < 0) {
|
||||
fprintf(stderr, "Failed to prepare statement");
|
||||
perror("");
|
||||
return 1;
|
||||
}
|
||||
|
||||
query_string = malloc(query_len);
|
||||
if (query_string == NULL) {
|
||||
fprintf(stderr, "Failed to allocate memory for the query");
|
||||
perror("");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ret = snprintf(query_string, query_len, query_template, table_name);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Failed to prepare statement");
|
||||
perror("");
|
||||
free(query_string);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sqlite3_prepare_v2(perm_database, query_string, -1, &perm_check_statement,
|
||||
NULL) != SQLITE_OK) {
|
||||
fprintf(stderr, "Failed to prepare statement: %s\n",
|
||||
sqlite3_errmsg(perm_database));
|
||||
free(query_string);
|
||||
return 1;
|
||||
}
|
||||
free(query_string);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void free_sql_queries(void) { sqlite3_finalize(perm_check_statement); }
|
||||
/**
|
||||
* Initializes the permanent permissions database.
|
||||
* Creates/opens the database file and sets up schema.
|
||||
* Initializes the permanent permissions table.
|
||||
*
|
||||
* @param db_filename Path to the SQLite database file
|
||||
* @return 0 on success, -1 on failure
|
||||
* @param db_filename: The filename of the permissions sqlite3 database
|
||||
* @return: 0 on success, -1 on failure
|
||||
*/
|
||||
int init_perm_permissions_table(const char *db_filename) {
|
||||
// Prevent group/others access during file creation
|
||||
// we don't want the group and others to access the db
|
||||
umask(0077);
|
||||
ruid = getuid();
|
||||
euid = geteuid();
|
||||
fprintf(stderr, "[ICFS] Running with uid: %d, gid: %d\n", euid, getegid());
|
||||
fprintf(stderr, "Running with uid: %d, gid: %d\n", euid, getegid());
|
||||
|
||||
// Open database with read/write access and full mutex protection
|
||||
if (sqlite3_open_v2(db_filename, &perm_database,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
|
||||
SQLITE_OPEN_FULLMUTEX,
|
||||
NULL)) {
|
||||
perror("[ICFS] Can't open permanent permissions database");
|
||||
perror("Can't open permanent permissions database");
|
||||
return -1;
|
||||
}
|
||||
umask(0); // Restore default umask
|
||||
|
||||
// Verify and initialize schema
|
||||
umask(0);
|
||||
if (ensure_database_schema()) {
|
||||
fprintf(stderr, "[ICFS] Database schema is not correct.\n");
|
||||
fprintf(stderr, "Database schema is not correct.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Switch to real UID, since we started with the icfs user to open the
|
||||
// database
|
||||
int status = seteuid(ruid);
|
||||
if (status < 0) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] Couldn't set euid to ruid during database setup.\n");
|
||||
fprintf(stderr, "Couldn't set euid to ruid.\n");
|
||||
exit(status);
|
||||
}
|
||||
|
||||
if (prepare_sql_queries()) {
|
||||
fprintf(stderr, "Couldn't prepare sql queries.\n");
|
||||
exit(status);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the database connection and releases resources.
|
||||
* Should be called when the module is shutting down.
|
||||
* Destroys the permanent permissions table.
|
||||
*/
|
||||
void destroy_perm_permissions_table(void) { sqlite3_close(perm_database); }
|
||||
|
||||
/**
|
||||
* Checks if a specific process has permanent access to a file.
|
||||
* Does not check parent processes.
|
||||
*
|
||||
* @param filename Path to the file being accessed
|
||||
* @param pi Process information structure
|
||||
* @return ALLOW (explicit allow), DENY (explicit deny), or NDEF (no info)
|
||||
*/
|
||||
access_t check_perm_access_noparent(const char *filename,
|
||||
struct process_info pi) {
|
||||
if (pi.name == NULL)
|
||||
return NDEF;
|
||||
|
||||
access_t ret = NDEF;
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
|
||||
// Query checks:
|
||||
// 1. Exact match for filename
|
||||
// 2. Directory prefix match (filename starts with stored path + '/')
|
||||
// Ordered by longest path for most specific match first
|
||||
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);
|
||||
|
||||
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 (step_ret == SQLITE_ROW) {
|
||||
int mode_col = sqlite3_column_int(stmt, 0);
|
||||
ret = mode_col ? ALLOW : DENY;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return ret;
|
||||
void destroy_perm_permissions_table(void) {
|
||||
free_sql_queries();
|
||||
sqlite3_close(perm_database);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a process or any of its ancestors have permanent access.
|
||||
* Handles hierarchical permission inheritance.
|
||||
* Checks if the process has a permanent access to the file.
|
||||
*
|
||||
* @param filename Path to the file being accessed
|
||||
* @param pi Process information structure
|
||||
* @return ALLOW/DENY/NDEF with NDEF meaning no explicit rule found
|
||||
* @note May return false negatives if parent processes terminate during check
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @return: 0 if access is denied, 1 if access is allowed
|
||||
*/
|
||||
access_t check_perm_access(const char *filename, struct process_info pi) {
|
||||
if (pi.PID == 0 || pi.name == NULL) {
|
||||
return NDEF;
|
||||
int check_perm_access(const char *filename, struct process_info pi) {
|
||||
size_t query_len =
|
||||
56 + strlen(table_name) + strlen(filename) + strlen(pi.name);
|
||||
const char *query = malloc(query_len);
|
||||
size_t should_be_written = snprintf(
|
||||
query, query_len,
|
||||
"SELECT * FROM %s WHERE executable = \'%s\' AND filename = \'%s\';",
|
||||
table_name, pi.name, filename);
|
||||
// -1 for the \0
|
||||
if (should_be_written != query_len - 1) {
|
||||
fprintf(stderr,
|
||||
"Unexpected query size while permanent access rule check: "
|
||||
"Expected %lu, but snprintf returned %lu. The query: %s\n",
|
||||
query_len, should_be_written, query);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct process_info current_pi = pi;
|
||||
current_pi.name = strdup(current_pi.name);
|
||||
|
||||
while (current_pi.PID != 0) {
|
||||
access_t access = check_perm_access_noparent(filename, current_pi);
|
||||
free(current_pi.name);
|
||||
|
||||
if (access != NDEF) {
|
||||
return access;
|
||||
}
|
||||
|
||||
current_pi.name = NULL;
|
||||
// Traverse to parent process
|
||||
while (current_pi.name == NULL) {
|
||||
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 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
char *sqlite_error = NULL;
|
||||
int flag = 0;
|
||||
int ret = sqlite3_exec(perm_database, query, set_flag, &flag, &sqlite_error);
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
|
||||
sqlite3_free(sqlite_error);
|
||||
free(query);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return NDEF;
|
||||
free(query);
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a permanent access rule for a process-file combination.
|
||||
* Gives permanent access to the process to the file.
|
||||
*
|
||||
* @param filename Path to the file needing access
|
||||
* @param pi 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
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @param pi: The process information
|
||||
* @return: 0 on success, 1 on failure
|
||||
*/
|
||||
int set_perm_access(const char *filename, struct process_info pi,
|
||||
set_mode_t mode) {
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
char *sql = NULL;
|
||||
|
||||
if (mode == SET_ALLOW) {
|
||||
sql = "INSERT INTO permissions VALUES (?1, ?2, TRUE);";
|
||||
} else if (mode == SET_DENY) {
|
||||
sql = "INSERT INTO permissions VALUES (?1, ?2, FALSE);";
|
||||
} else {
|
||||
return 1; // Invalid mode
|
||||
}
|
||||
|
||||
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);
|
||||
int give_perm_access(const char *filename, struct process_info pi) {
|
||||
size_t query_len =
|
||||
30 + strlen(table_name) + strlen(filename) + strlen(pi.name);
|
||||
const char *query = malloc(query_len);
|
||||
size_t should_be_written =
|
||||
snprintf(query, query_len, "INSERT INTO %s VALUES (\'%s\', \'%s\');",
|
||||
table_name, pi.name, filename);
|
||||
// -1 for the \0
|
||||
if (should_be_written != query_len - 1) {
|
||||
fprintf(stderr,
|
||||
"Unexpected query size while permanent access rule insertion: "
|
||||
"Expected %lu, but snprintf returned %lu\n",
|
||||
query_len, should_be_written);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
char *sqlite_error = NULL;
|
||||
int ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error);
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
|
||||
sqlite3_free(sqlite_error);
|
||||
free(query);
|
||||
return 1;
|
||||
}
|
||||
|
||||
free(query);
|
||||
return 0;
|
||||
}
|
||||
|
@ -9,45 +9,37 @@
|
||||
#ifndef PERM_PERMISSION_TABLE_H
|
||||
#define PERM_PERMISSION_TABLE_H
|
||||
|
||||
#include "access_t.h"
|
||||
#include "process_info.h"
|
||||
#include "set_mode_t.h"
|
||||
|
||||
/**
|
||||
* Initializes the permanent permissions database.
|
||||
* Creates/opens the database file and sets up schema.
|
||||
* Initializes the permanent permissions table.
|
||||
*
|
||||
* @param db_filename Path to the SQLite database file
|
||||
* @return 0 on success, -1 on failure
|
||||
* @param db_filename: The filename of the permissions sqlite3 database
|
||||
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
|
||||
*/
|
||||
int init_perm_permissions_table(const char *db_filename);
|
||||
|
||||
/**
|
||||
* Closes the database connection and releases resources.
|
||||
* Should be called when the module is shutting down.
|
||||
* Destroys the permanent permissions table.
|
||||
*/
|
||||
void destroy_perm_permissions_table();
|
||||
|
||||
/**
|
||||
* Checks if a process or any of its ancestors have permanent access.
|
||||
* Handles hierarchical permission inheritance.
|
||||
* Checks if the process has a permanent access to the file.
|
||||
*
|
||||
* @param filename Path to the file being accessed
|
||||
* @param pi Process information structure
|
||||
* @return ALLOW/DENY/NDEF with NDEF meaning no explicit rule found
|
||||
* @note May return false negatives if parent processes terminate during check
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @return: 0 if access is denied, 1 if access is allowed
|
||||
*/
|
||||
access_t check_perm_access(const char *filename, struct process_info pi);
|
||||
int check_perm_access(const char *filename, struct process_info pi);
|
||||
|
||||
/**
|
||||
* Sets a permanent access rule for a process-file combination.
|
||||
* Gives permanent access to the process to the file.
|
||||
*
|
||||
* @param filename Path to the file needing access
|
||||
* @param pi Process information
|
||||
* @param mode SET_ALLOW (whitelist) or SET_DENY (blacklist)
|
||||
* @return 0 on success, 1 on failure
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @param pi: The process information
|
||||
* @return: 0 on success, -1 on failure
|
||||
*/
|
||||
int set_perm_access(const char *filename, struct process_info pi,
|
||||
set_mode_t mode);
|
||||
int give_perm_access(const char *filename, struct process_info pi);
|
||||
|
||||
#endif // #ifdef PERM_PERMISSION_TABLE_H
|
||||
|
@ -1,153 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
*
|
||||
* In Linux, threads within the same process share the same thread group ID
|
||||
* (TGID), which is equal to the PID of the main thread. This function retrieves
|
||||
* the TGID by parsing `/proc/<tid>/status`, effectively returning the main
|
||||
* thread's PID.
|
||||
*
|
||||
* @param tid The thread ID (TID) of any thread in the process.
|
||||
* @return pid_t The process ID (main thread's PID), or 0 on error (invalid tid
|
||||
* or file read failure).
|
||||
*/
|
||||
pid_t get_main_thread_pid(pid_t tid) {
|
||||
// Validate input
|
||||
if (tid <= 0) {
|
||||
// Invalid TID: TIDs are always positive in Linux
|
||||
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);
|
||||
|
||||
// Debugging check: If the provided tid is not the main thread's tid,
|
||||
// this logs a message but does not affect the return value.
|
||||
// This is useful for detecting cases where non-main threads are being
|
||||
// inspected.
|
||||
if (tgid != tid) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] The tid and and pid wasn't equal. tid:%d, pid:%d.\n", tid,
|
||||
tgid);
|
||||
}
|
||||
|
||||
return tgid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves the full path of the executable for a given process ID.
|
||||
*
|
||||
* This function reads the `/proc/<pid>/exe` symbolic link, which points to the
|
||||
* executable file of the process. The returned string is dynamically allocated
|
||||
* and must be freed by the caller.
|
||||
*
|
||||
* @param pid The process ID to query.
|
||||
* @return char* Dynamically allocated string containing the executable path, or
|
||||
* NULL on failure.
|
||||
*/
|
||||
char *get_process_name_by_pid(const int pid) {
|
||||
char path[1024];
|
||||
sprintf(path, "/proc/%d/exe", pid);
|
||||
|
||||
size_t size = 128;
|
||||
char *name = malloc(size);
|
||||
if (name == NULL) {
|
||||
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 the buffer was too small, double its size and try again
|
||||
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 null-terminate, so we must do it manually
|
||||
name[len] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds the parent process ID of a given process.
|
||||
*
|
||||
* This function parses the `/proc/<pid>/status` file to extract the `PPid`
|
||||
* field, which represents the parent process ID. This is useful for tracing
|
||||
* process lineage.
|
||||
*
|
||||
* @param pid The process ID of the process to find the parent of.
|
||||
* @return pid_t The parent process ID, or 0 if the parent could not be
|
||||
* determined.
|
||||
*/
|
||||
pid_t get_parent_pid(pid_t pid) {
|
||||
pid_t ppid = 0;
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "/proc/%d/status",
|
||||
pid); // Use %d for signed pid_t
|
||||
|
||||
FILE *file = fopen(path, "r");
|
||||
if (file == NULL) {
|
||||
perror("[ICFS] Failed to open /proc/<pid>/status");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), file)) {
|
||||
// The "PPid:" field in `/proc/<pid>/status` is followed by a tab character
|
||||
if (sscanf(line, "PPid:\t%d", &ppid) == 1) {
|
||||
fclose(file);
|
||||
return ppid;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return 0; // Parent PID not found
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include <time.h>
|
||||
|
||||
/**
|
||||
* @brief Retrieves the full path of the executable for a given process ID.
|
||||
*
|
||||
* This function reads the `/proc/<pid>/exe` symbolic link, which points to the
|
||||
* executable file of the process. The returned string is dynamically allocated
|
||||
* and must be freed by the caller.
|
||||
*
|
||||
* @param pid The process ID to query.
|
||||
* @return char* Dynamically allocated string containing the executable path, or
|
||||
* NULL on failure.
|
||||
*/
|
||||
char *get_process_name_by_pid(const int pid);
|
||||
|
||||
/**
|
||||
* @brief Finds the parent process ID of a given process.
|
||||
*
|
||||
* This function parses the `/proc/<pid>/status` file to extract the `PPid`
|
||||
* field, which represents the parent process ID. This is useful for tracing
|
||||
* process lineage.
|
||||
*
|
||||
* @param pid The process ID of the process to find the parent of.
|
||||
* @return pid_t The parent process ID, or 0 if the parent could not be
|
||||
* determined.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* In Linux, threads within the same process share the same thread group ID
|
||||
* (TGID), which is equal to the PID of the main thread. This function retrieves
|
||||
* the TGID by parsing `/proc/<tid>/status`, effectively returning the main
|
||||
* thread's PID.
|
||||
*
|
||||
* @param tid The thread ID (TID) of any thread in the process.
|
||||
* @return pid_t The process ID (main thread's PID), or 0 on error (invalid tid
|
||||
* or file read failure).
|
||||
*/
|
||||
pid_t get_main_thread_pid(pid_t tid);
|
||||
|
||||
#endif // !PROC_OPERATIONS
|
@ -9,25 +9,10 @@
|
||||
#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;
|
||||
const 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,26 +0,0 @@
|
||||
/*
|
||||
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
|
||||
|
||||
/**
|
||||
* @brief Build a real path to the file.
|
||||
*
|
||||
* @param filename Relative path within the filesystem
|
||||
* @return Newly allocated absolute path (caller must free)
|
||||
*/
|
||||
const char *real_filename(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Get the current mount point path
|
||||
* @return Absolute path to the filesystem root
|
||||
*/
|
||||
const char *get_mountpoint(void);
|
||||
|
||||
#endif // !REAL_FILENAME_H
|
@ -1,12 +0,0 @@
|
||||
/*
|
||||
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
|
||||
typedef enum { SET_DENY, SET_ALLOW } set_mode_t;
|
||||
#endif // !SET_MODE_T_H
|
179
src/sourcefs.c
179
src/sourcefs.c
@ -10,194 +10,80 @@
|
||||
|
||||
#include "sourcefs.h"
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* @brief Global handle for the source filesystem implementation
|
||||
*
|
||||
* Stores the root directory information to enable relative path operations.
|
||||
*/
|
||||
static struct source_files_handle {
|
||||
char *mountpoint; // Absolute path to the mounted filesystem root
|
||||
int root_fd; // File descriptor for the root directory (O_PATH)
|
||||
int root_fd;
|
||||
} handle;
|
||||
|
||||
/**
|
||||
* @brief Translate FUSE paths to real paths in the underlying filesystem
|
||||
*
|
||||
* FUSE passes paths starting with '/' (e.g., "/dir/file"), but we need to
|
||||
* operate relative to our root_fd. This function converts:
|
||||
* - "/" -> "." (current directory)
|
||||
* - "/dir/file" -> "dir/file"
|
||||
*
|
||||
* @param filename Path from FUSE (always starts with '/')
|
||||
* @return Relative path suitable for use with root_fd
|
||||
*/
|
||||
const char *source_filename_translate(const char *filename) {
|
||||
if (strcmp("/", filename) == 0) {
|
||||
return ".";
|
||||
} else {
|
||||
return filename + 1;
|
||||
}
|
||||
return filename + 1; // Skip leading slash
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the source filesystem with a root directory
|
||||
*
|
||||
* Sets up the mount point and opens a protected file descriptor to the
|
||||
* root directory for safe relative operations.
|
||||
*
|
||||
* @param root_path Absolute path to the physical root directory
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int source_init(const char *root_path) {
|
||||
// Allocate memory for mount point path
|
||||
handle.mountpoint = malloc(strlen(root_path) + 1);
|
||||
if (handle.mountpoint == NULL) {
|
||||
perror("[ICFS] Malloc failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
strcpy(handle.mountpoint, root_path);
|
||||
|
||||
// Open root directory with O_PATH to prevent accidental reads/writes
|
||||
// while maintaining a valid descriptor for relative operations
|
||||
int root_fd = open(root_path, O_PATH);
|
||||
|
||||
if (root_fd == -1) {
|
||||
fprintf(stderr, "[ICFS] Could not initialize source file system at %s",
|
||||
root_path);
|
||||
fprintf(stderr, "Could not initialize source file system at %s", root_path);
|
||||
perror("");
|
||||
return -1;
|
||||
}
|
||||
|
||||
handle.root_fd = root_fd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean up resources used by the source filesystem
|
||||
*/
|
||||
void source_destroy(void) {
|
||||
free(handle.mountpoint); // Free allocated mount point path
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the current mount point path
|
||||
* @return Absolute path to the filesystem root
|
||||
*/
|
||||
const char *get_mountpoint(void) { return handle.mountpoint; }
|
||||
|
||||
/**
|
||||
* @brief Build a real path to the file.
|
||||
*
|
||||
* @param filename Relative path within the filesystem
|
||||
* @return Newly allocated absolute path (caller must free)
|
||||
*/
|
||||
const char *real_filename(const char *filename) {
|
||||
const char *mountpoint = get_mountpoint();
|
||||
size_t len1 = strlen(mountpoint);
|
||||
size_t len2 = strlen(filename);
|
||||
|
||||
// Allocate space for combined path + null terminator
|
||||
char *result = malloc(len1 + len2 + 1);
|
||||
if (result == NULL) {
|
||||
fprintf(stderr, "[ICFS] Memory allocation failed");
|
||||
perror("");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strcpy(result, mountpoint);
|
||||
strcat(result, filename);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a directory in the filesystem
|
||||
*
|
||||
* Uses mkdirat() to safely create directories relative to root_fd,
|
||||
* preventing race conditions from concurrent path modifications.
|
||||
*/
|
||||
int source_mkdir(const char *filename, mode_t mode) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return mkdirat(handle.root_fd, relative_filename, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove a file from the filesystem
|
||||
*
|
||||
* Uses unlinkat() to safely remove files relative to the root directory.
|
||||
*/
|
||||
int source_unlink(const char *filename) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return unlinkat(handle.root_fd, relative_filename, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get file status information
|
||||
*
|
||||
* Uses fstatat() to retrieve metadata relative to root_fd. Follows symlinks
|
||||
* by default (flags=0).
|
||||
*/
|
||||
int source_stat(const char *restrict filename, struct stat *restrict statbuf) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return fstatat(handle.root_fd, relative_filename, statbuf, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove an empty directory
|
||||
*
|
||||
* Uses unlinkat() with AT_REMOVEDIR flag to safely remove directories.
|
||||
*/
|
||||
int source_rmdir(const char *filename) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return unlinkat(handle.root_fd, relative_filename, AT_REMOVEDIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a symbolic link
|
||||
*
|
||||
* Creates symlinks relative to the root_fd directory for safety.
|
||||
*/
|
||||
int source_symlink(const char *target, const char *linkpath) {
|
||||
const char *relative_linkpath = source_filename_translate(linkpath);
|
||||
return symlinkat(target, handle.root_fd, relative_linkpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check file access permissions
|
||||
*
|
||||
* Uses faccessat() to check access rights relative to root_fd.
|
||||
*/
|
||||
int source_access(const char *filename, int mode) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return faccessat(handle.root_fd, relative_filename, mode, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open a directory for reading
|
||||
*
|
||||
* Combines openat() and fdopendir() to safely access directories relative
|
||||
* to the root_fd.
|
||||
*/
|
||||
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("[ICFS] Openat failed");
|
||||
perror("Openat failed");
|
||||
return NULL;
|
||||
}
|
||||
DIR *dir_pointer = fdopendir(fd);
|
||||
return dir_pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Rename a file or directory
|
||||
*
|
||||
* Uses renameat() to safely rename within the same root_fd namespace.
|
||||
*/
|
||||
int source_rename(const char *oldpath, const char *newpath) {
|
||||
const char *relative_oldpath = source_filename_translate(oldpath);
|
||||
const char *relative_newpath = source_filename_translate(newpath);
|
||||
@ -205,80 +91,41 @@ int source_rename(const char *oldpath, const char *newpath) {
|
||||
relative_newpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a hard link
|
||||
*
|
||||
* Uses linkat() with flags=0 (default behavior). May need AT_SYMLINK_NOFOLLOW
|
||||
* if symlink handling should be modified.
|
||||
*/
|
||||
int source_link(const char *oldpath, const char *newpath) {
|
||||
const char *relative_oldpath = source_filename_translate(oldpath);
|
||||
const char *relative_newpath = source_filename_translate(newpath);
|
||||
return linkat(handle.root_fd, relative_oldpath, handle.root_fd,
|
||||
relative_newpath, 0);
|
||||
// NOTE: perhaps the flags here need to be reevaluated.
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Change file access mode
|
||||
*
|
||||
* Uses fchmodat() with flags=0 (follow symlinks). Consider using
|
||||
* AT_SYMLINK_NOFOLLOW if symlink metadata should be modified directly.
|
||||
*/
|
||||
int source_chmod(const char *filename, mode_t mode) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return fchmodat(handle.root_fd, relative_filename, mode, 0);
|
||||
// NOTE: perhaps the flags here need to be reevaluated.
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Change file owner and group
|
||||
*
|
||||
* Uses fchownat() with AT_SYMLINK_NOFOLLOW to modify symlink metadata
|
||||
* rather than its target.
|
||||
*/
|
||||
int source_chown(const char *filename, uid_t owner, gid_t group) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return fchownat(handle.root_fd, filename, owner, group, AT_SYMLINK_NOFOLLOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Truncate a file to a specified length
|
||||
*
|
||||
* Opens the file with read-only access then truncates it. This may fail
|
||||
* if the file wasn't opened with write permissions. Consider changing
|
||||
* openat() flags to O_WRONLY for reliability.
|
||||
*/
|
||||
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("[ICFS] Openat failed");
|
||||
perror("Openat failed");
|
||||
return -1;
|
||||
}
|
||||
return ftruncate(fd, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open a file with specified flags
|
||||
*
|
||||
* Uses openat() to safely access files relative to root_fd.
|
||||
*/
|
||||
int source_open(const char *filename, int flags) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return openat(handle.root_fd, relative_filename, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create and open a new file
|
||||
*
|
||||
* Uses openat() with O_CREAT to create files relative to root_fd.
|
||||
*/
|
||||
int source_create(const char *filename, int flags, mode_t mode) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return openat(handle.root_fd, relative_filename, flags | O_CREAT, mode);
|
||||
}
|
||||
|
||||
int source_utimens(const char *filename, const struct timespec ts[2],
|
||||
int flags) {
|
||||
const char *relative_filename = source_filename_translate(filename);
|
||||
return utimensat(handle.root_fd, relative_filename, ts, AT_SYMLINK_NOFOLLOW);
|
||||
return openat(handle.root_fd, relative_filename, flags, mode);
|
||||
}
|
||||
|
@ -13,132 +13,47 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
/**
|
||||
* @brief Initialize the source filesystem with a root directory
|
||||
* Initializes the source file handling.
|
||||
*
|
||||
* Sets up the mount point and opens a protected file descriptor to the
|
||||
* root directory for safe relative operations.
|
||||
*
|
||||
* @param root_path Absolute path to the physical root directory
|
||||
* @return 0 on success, -1 on failure
|
||||
* @param root_path The root of the source files folder.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int source_init(const char *root_path);
|
||||
|
||||
/**
|
||||
* @brief Clean up resources used by the source filesystem
|
||||
*/
|
||||
void source_destroy(void);
|
||||
/* All of the functions below are designed to behave exactly as their non-source
|
||||
* counterparts. */
|
||||
|
||||
/**
|
||||
* @brief Get file status information
|
||||
*
|
||||
* Uses fstatat() to retrieve metadata relative to root_fd. Follows symlinks
|
||||
* by default (flags=0).
|
||||
*/
|
||||
int source_stat(const char *restrict filename, struct stat *restrict statbuf);
|
||||
|
||||
/**
|
||||
* @brief Open a directory for reading
|
||||
*
|
||||
* Combines openat() and fdopendir() to safely access directories relative
|
||||
* to the root_fd.
|
||||
*/
|
||||
struct dirent *source_readdir(DIR *dirp);
|
||||
|
||||
DIR *source_opendir(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Remove a file from the filesystem
|
||||
*
|
||||
* Uses unlinkat() with AT_REMOVEDIR flag to safely remove files relative
|
||||
* to the root directory.
|
||||
*/
|
||||
int source_unlink(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Create a directory in the filesystem
|
||||
*
|
||||
* Uses mkdirat() to safely create directories relative to root_fd,
|
||||
* preventing race conditions from concurrent path modifications.
|
||||
*/
|
||||
int source_mkdir(const char *filename, mode_t mode);
|
||||
|
||||
/**
|
||||
* @brief Remove an empty directory
|
||||
*
|
||||
* Uses unlinkat() with AT_REMOVEDIR flag to safely remove directories.
|
||||
*/
|
||||
int source_rmdir(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Create a symbolic link
|
||||
*
|
||||
* Creates symlinks relative to the root_fd directory for safety.
|
||||
*/
|
||||
int source_symlink(const char *target, const char *linkpath);
|
||||
|
||||
/**
|
||||
* @brief Rename a file or directory
|
||||
*
|
||||
* Uses renameat() to safely rename within the same root_fd namespace.
|
||||
*/
|
||||
int source_rename(const char *oldpath, const char *newpath);
|
||||
|
||||
/**
|
||||
* @brief Create a hard link
|
||||
*
|
||||
* Uses linkat() with flags=0 (default behavior). May need AT_SYMLINK_NOFOLLOW
|
||||
* if symlink handling should be modified.
|
||||
*/
|
||||
int source_link(const char *oldpath, const char *newpath);
|
||||
|
||||
/**
|
||||
* @brief Change file access mode
|
||||
*
|
||||
* Uses fchmodat() with flags=0 (follow symlinks). Consider using
|
||||
* AT_SYMLINK_NOFOLLOW if symlink metadata should be modified directly.
|
||||
*/
|
||||
int source_chmod(const char *filename, mode_t mode);
|
||||
|
||||
/**
|
||||
* @brief Change file owner and group
|
||||
*
|
||||
* Uses fchownat() with AT_SYMLINK_NOFOLLOW to modify symlink metadata
|
||||
* rather than its target.
|
||||
*/
|
||||
int source_chown(const char *filename, uid_t owner, gid_t group);
|
||||
|
||||
/**
|
||||
* @brief Truncate a file to a specified length
|
||||
*
|
||||
* Opens the file with read-only access then truncates it. This may fail
|
||||
* if the file wasn't opened with write permissions. Consider changing
|
||||
* openat() flags to O_WRONLY for reliability.
|
||||
*/
|
||||
int source_truncate(const char *filename, off_t length);
|
||||
|
||||
/**
|
||||
* @brief Check file access permissions
|
||||
*
|
||||
* Uses faccessat() to check access rights relative to root_fd.
|
||||
*/
|
||||
int source_access(const char *filename, int mode);
|
||||
|
||||
/* `open` and `create` are designed to correspond to fuse operations, not the
|
||||
* libc's `open(2)`. Both of them actually call `openat`. */
|
||||
|
||||
/**
|
||||
* @brief Open a file with specified flags
|
||||
*
|
||||
* Uses openat() to safely access files relative to root_fd.
|
||||
*/
|
||||
int source_open(const char *filename, int flags);
|
||||
|
||||
/**
|
||||
* @brief Create and open a new file
|
||||
*
|
||||
* Uses openat() with O_CREAT to create files relative to root_fd.
|
||||
*/
|
||||
int source_create(const char *filename, int flags, mode_t mode);
|
||||
|
||||
int source_utimens(const char *filename, const struct timespec ts[2],
|
||||
int flags);
|
||||
|
||||
#endif // !SOURCEFS_H
|
||||
|
@ -7,78 +7,34 @@
|
||||
*/
|
||||
|
||||
#include "temp_permissions_table.h"
|
||||
#include "access_t.h"
|
||||
#include "cc.h"
|
||||
#include "proc_operations.h"
|
||||
#include "process_info.h"
|
||||
#include <pthread.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define GC_INTERVAL 1 // Garbage collection time interval.
|
||||
|
||||
/**
|
||||
* @struct temp_process_permissions
|
||||
* @brief Stores temporary file access permissions for a process
|
||||
*
|
||||
* This structure tracks temporary file access permissions for a specific
|
||||
* process. The permissions are tied to the process's lifetime using its
|
||||
* creation time.
|
||||
*/
|
||||
struct temp_process_permissions {
|
||||
unsigned long long creation_time; /**< Process creation time */
|
||||
vec(char *) allowed_files; /**< List of allowed file paths (prefixes) */
|
||||
vec(char *) denied_files; /**< List of denied file paths (prefixes) */
|
||||
// yes, this is a correct type for start time in jiffies (see
|
||||
// proc_pid_stat(5))
|
||||
unsigned long long creation_time;
|
||||
vec(char *) allowed_files;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Global table mapping PIDs to temporary permissions
|
||||
*
|
||||
* This map stores the temporary file access permissions for processes.
|
||||
* Entries are keyed by process ID (pid_t) and contain temp_process_permissions
|
||||
* structures with the access rules.
|
||||
*/
|
||||
map(pid_t, struct temp_process_permissions) temp_permissions_table;
|
||||
pthread_mutex_t temp_permissions_table_lock;
|
||||
|
||||
/**
|
||||
* @brief Read-write lock for thread-safe access to temp_permissions_table
|
||||
*
|
||||
* A read-write lock to ensure thread-safe operations on the
|
||||
* temp_permissions_table. Allows concurrent reads but exclusive writes.
|
||||
*/
|
||||
pthread_rwlock_t temp_permissions_table_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||
|
||||
/**
|
||||
* @brief Thread handle for the garbage collector
|
||||
*
|
||||
* This thread handles cleanup of stale entries in the temp_permissions_table.
|
||||
*/
|
||||
pthread_t gc_thread;
|
||||
|
||||
/**
|
||||
* @brief Flag indicating whether garbage collector is active
|
||||
*
|
||||
* When non-zero, indicates that the garbage collector thread should continue
|
||||
* running.
|
||||
*/
|
||||
int is_gc_active = 0;
|
||||
|
||||
/**
|
||||
* Function to get the process creation time from the proc
|
||||
* Function to get the process creation time (in jiffies) from the proc
|
||||
* filesystem
|
||||
*
|
||||
* @param pid The process ID of the process to get the creation time of
|
||||
* @return The process creation time, or 0 on error
|
||||
* @note although nothing in the documentation says that the creation time is
|
||||
* @param pid: The process ID of the process to get the creation time of
|
||||
* @return: The process creation time in jiffies, or 0 on error
|
||||
* @note: although nothing in the documentation says that the creation time is
|
||||
* never really equal to 0, it exceptionally unlikely.
|
||||
*/
|
||||
unsigned long long get_process_creation_time(pid_t pid) {
|
||||
char path[256];
|
||||
FILE *fp = NULL;
|
||||
char path[32];
|
||||
FILE *fp;
|
||||
unsigned long long creation_time = 0;
|
||||
|
||||
// Construct the path to the process's status file
|
||||
@ -87,21 +43,20 @@ unsigned long long get_process_creation_time(pid_t pid) {
|
||||
// Open the status file
|
||||
fp = fopen(path, "r");
|
||||
if (fp == NULL) {
|
||||
perror("[ICFS] fopen");
|
||||
perror("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,
|
||||
"[ICFS] Error reading process stat file on the number %d\n", i);
|
||||
fprintf(stderr, "Error reading process stat file on the number %d\n", i);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (fscanf(fp, "%llu", &creation_time) != 1) {
|
||||
fprintf(stderr, "[ICFS] Error reading creation time\n");
|
||||
fprintf(stderr, "Error reading creation time\n");
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
@ -112,224 +67,130 @@ unsigned long long get_process_creation_time(pid_t pid) {
|
||||
return creation_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validates if a process entry is still valid
|
||||
*
|
||||
* Checks if the given process entry matches the current process state
|
||||
* by comparing creation times.
|
||||
*
|
||||
* @param pid Process ID to validate
|
||||
* @param entry Pointer to the permission entry to validate
|
||||
* @return 1 if valid, 0 if invalid or error
|
||||
*/
|
||||
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 the creation time doesn't match, this is a different process instance
|
||||
if (creation_time != entry->creation_time) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Garbage collector thread for cleaning up stale entries
|
||||
*
|
||||
* Periodically scans the permissions table and removes entries for processes
|
||||
* that no longer exist. Runs every second while is_gc_active is true.
|
||||
*
|
||||
* @param arg Unused thread argument parameter
|
||||
* @return Always NULL
|
||||
*/
|
||||
void *garbage_collector(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
while (is_gc_active) {
|
||||
sleep(GC_INTERVAL); // Check once per second for stale entries
|
||||
pthread_rwlock_wrlock(&temp_permissions_table_lock);
|
||||
|
||||
vec(pid_t) blacklist;
|
||||
init(&blacklist);
|
||||
|
||||
// Identify stale entries
|
||||
for_each(&temp_permissions_table, pid, entry) {
|
||||
if (!is_valid(*pid, entry)) {
|
||||
push(&blacklist, *pid);
|
||||
// Free memory for this entry's file lists
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all stale entries from the table
|
||||
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
|
||||
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
|
||||
*/
|
||||
int init_temp_permissions_table(void) {
|
||||
int init_temp_permissions_table() {
|
||||
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
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @note the table is guranteed to be destroyed if it is already initialized.
|
||||
* It does not indicate any errors whatsoever. If something goes wrong - you are
|
||||
* screwed.
|
||||
* @note: the table is guranteed to be destroyed if it is already initialized
|
||||
*/
|
||||
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
|
||||
void destroy_temp_permissions_table() {
|
||||
// free the memory allocated for the table
|
||||
for_each(&temp_permissions_table, entry) {
|
||||
for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); }
|
||||
cleanup(&entry->allowed_files);
|
||||
}
|
||||
for_each(&temp_permissions_table, entry) {
|
||||
for_each(&entry->denied_files, denied_file) { free(*denied_file); }
|
||||
cleanup(&entry->denied_files);
|
||||
}
|
||||
|
||||
cleanup(&temp_permissions_table);
|
||||
pthread_mutex_destroy(&temp_permissions_table_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a specific process has temporary access to a file
|
||||
* Checks if the process has a temporary access to the file.
|
||||
*
|
||||
* Checks only the specified process (not its parents) for temporary access
|
||||
* permissions to the given file. Uses a longest-match algorithm for path
|
||||
* prefixes.
|
||||
*
|
||||
* @param filename Path to the file being accessed
|
||||
* @param pid Process ID of the process to check
|
||||
* @return Access status ALLOW, DENY, or NDEF (no information)
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pid: PID of the process
|
||||
* @return: 0 if access is denied, 1 if access is allowed
|
||||
*/
|
||||
access_t check_temp_access_noparent(const char *filename, pid_t pid) {
|
||||
pthread_rwlock_rdlock(&temp_permissions_table_lock);
|
||||
|
||||
int check_temp_access_noparent(const char *filename, pid_t pid) {
|
||||
// TODO: more efficient locking
|
||||
pthread_mutex_lock(&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("[ICFS] Could not retrieve process creation time");
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
return NDEF;
|
||||
perror("Could not retrieve process creation time");
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (process_creation_time == permission_entry->creation_time) {
|
||||
// The process is the same as the one that was granted temporary access
|
||||
size_t filename_len = strlen(filename);
|
||||
access_t ret = NDEF;
|
||||
size_t maxlen = 0;
|
||||
|
||||
// Check denied files first (deny takes precedence over allow)
|
||||
for_each(&permission_entry->denied_files, denied_file) {
|
||||
size_t denied_file_len = strlen(*denied_file);
|
||||
// Check if this denied path prefix matches the requested 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))) &&
|
||||
denied_file_len > maxlen) {
|
||||
maxlen = denied_file_len;
|
||||
ret = DENY;
|
||||
}
|
||||
}
|
||||
|
||||
// Check allowed files
|
||||
// the process is the same as the one that was granted temporary access
|
||||
// to the file
|
||||
for_each(&permission_entry->allowed_files, allowed_file) {
|
||||
size_t allowed_file_len = strlen(*allowed_file);
|
||||
// Check if this allowed path prefix matches the requested 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))) &&
|
||||
allowed_file_len > maxlen) {
|
||||
maxlen = allowed_file_len;
|
||||
ret = ALLOW;
|
||||
if (strncmp(*allowed_file, filename, strlen(filename)) == 0) {
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
return NDEF;
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the parent process ID of a given process.
|
||||
*
|
||||
* @param pid: The process ID of the process to find the parent of
|
||||
* @return: The parent process ID, or 0 if the parent process ID could not be
|
||||
* found
|
||||
*/
|
||||
pid_t get_parent_pid(pid_t pid) {
|
||||
pid_t ppid = 0;
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "/proc/%u/status", pid);
|
||||
|
||||
FILE *file = fopen(path, "r");
|
||||
if (file == NULL) {
|
||||
perror("Failed to open /proc/<pid>/status");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), file)) {
|
||||
if (sscanf(line, "PPid:\t%d", &ppid) == 1) {
|
||||
fclose(file);
|
||||
return ppid;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return 0; // Parent PID not found
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the process or any of it's parents have temporary access to the
|
||||
* file.
|
||||
*
|
||||
* @param filename The file that the process is trying to access
|
||||
* @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
|
||||
* execution the result is not guranteed to be correct. It should only
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram pi: The process information
|
||||
* @return: 0 if access is denied, 1 if access is allowed
|
||||
* @note: In case one of the parent processes is killed while this function
|
||||
* execution the result is not guranteed to be correct. It should only lead to
|
||||
* false negatives, though.
|
||||
*/
|
||||
access_t check_temp_access(const char *filename, struct process_info pi) {
|
||||
int check_temp_access(const char *filename, struct process_info pi) {
|
||||
pid_t current_pid = pi.PID;
|
||||
while (current_pid != 0) {
|
||||
access_t access = check_temp_access_noparent(filename, current_pid);
|
||||
if (access != NDEF) {
|
||||
return access;
|
||||
if (check_temp_access_noparent(filename, current_pid)) {
|
||||
return 1;
|
||||
}
|
||||
current_pid = get_main_thread_pid(get_parent_pid(current_pid));
|
||||
current_pid = get_parent_pid(current_pid);
|
||||
}
|
||||
|
||||
return NDEF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets temporary access mode of the process to the file.
|
||||
* Gives temporary access to the process to the file.
|
||||
*
|
||||
* @param filename The file that the process is trying to access
|
||||
* @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.
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @param pi: The process information
|
||||
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
|
||||
*/
|
||||
int set_temp_access(const char *filename, struct process_info pi,
|
||||
set_mode_t mode) {
|
||||
if (pi.PID == 0)
|
||||
return NDEF;
|
||||
pthread_rwlock_wrlock(&temp_permissions_table_lock);
|
||||
int give_temp_access(const char *filename, struct process_info pi) {
|
||||
pthread_mutex_lock(&temp_permissions_table_lock);
|
||||
struct temp_process_permissions *permission_entry =
|
||||
get(&temp_permissions_table, pi.PID);
|
||||
|
||||
@ -338,45 +199,34 @@ 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("[ICFS] Could not retrieve process creation time");
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
perror("Could not retrieve process creation time");
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (process_creation_time == permission_entry->creation_time) {
|
||||
// The process is the same as the one that was granted temporary access
|
||||
if (mode == SET_ALLOW) {
|
||||
push(&permission_entry->allowed_files, strdup(filename));
|
||||
}
|
||||
if (mode == SET_DENY) {
|
||||
push(&permission_entry->denied_files, strdup(filename));
|
||||
}
|
||||
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
// the process is the same as the one that was granted temporary access
|
||||
// to the file
|
||||
push(&permission_entry->allowed_files, strdup(filename));
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
return 0;
|
||||
}
|
||||
// We have an entry for the process, but the process is different
|
||||
// Delete the entry and create a new one
|
||||
// we have an entry for the process, but the process is different
|
||||
// delete the entry and create a new one
|
||||
erase(&temp_permissions_table, pi.PID);
|
||||
permission_entry = NULL;
|
||||
}
|
||||
|
||||
// No entry is present - construct a new one
|
||||
// no entry is present
|
||||
// construct the entry
|
||||
struct temp_process_permissions new_permission_entry;
|
||||
|
||||
new_permission_entry.creation_time = get_process_creation_time(pi.PID);
|
||||
init(&new_permission_entry.allowed_files);
|
||||
init(&new_permission_entry.denied_files);
|
||||
|
||||
if (mode == SET_ALLOW) {
|
||||
push(&new_permission_entry.allowed_files, strdup(filename));
|
||||
}
|
||||
if (mode == SET_DENY) {
|
||||
push(&new_permission_entry.denied_files, strdup(filename));
|
||||
}
|
||||
push(&new_permission_entry.allowed_files, strdup(filename));
|
||||
|
||||
insert(&temp_permissions_table, pi.PID, new_permission_entry);
|
||||
|
||||
pthread_rwlock_unlock(&temp_permissions_table_lock);
|
||||
pthread_mutex_unlock(&temp_permissions_table_lock);
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,65 +1,39 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "access_t.h"
|
||||
#include "process_info.h"
|
||||
#include "set_mode_t.h"
|
||||
|
||||
/**
|
||||
* Initializes the temporary permissions table.
|
||||
*
|
||||
* @return: 0 on success, -1 on failure
|
||||
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
|
||||
*/
|
||||
int init_temp_permissions_table(void);
|
||||
|
||||
/**
|
||||
* Starts the temporary permissions table garbage_collector.
|
||||
*
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int init_garbage_collector(void);
|
||||
int init_temp_permissions_table();
|
||||
|
||||
/**
|
||||
* Destroys the temporary permissions table.
|
||||
*
|
||||
* @note the table is guranteed to be destroyed if it is already initialized.
|
||||
* It does not indicate any errors whatsoever. If something goes wrong - you are
|
||||
* screwed.
|
||||
* @note: the table is guranteed to be destroyed if it is already initialized
|
||||
*/
|
||||
void destroy_temp_permissions_table(void);
|
||||
void destroy_temp_permissions_table();
|
||||
|
||||
/**
|
||||
* Checks if the process or any of it's parents have temporary access to the
|
||||
* file.
|
||||
* Checks if the process has a temporary access to the file.
|
||||
*
|
||||
* @param filename The file that the process is trying to access
|
||||
* @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
|
||||
* execution the result is not guranteed to be correct. It should only
|
||||
* false negatives, though.
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @param pi: The process information
|
||||
* @return: 0 if access is denied, 1 if access is allowed
|
||||
*/
|
||||
access_t check_temp_access(const char *filename, struct process_info pi);
|
||||
int check_temp_access(const char *filename, struct process_info pi);
|
||||
|
||||
/**
|
||||
* Sets temporary access mode of the process to the file.
|
||||
* Gives temporary access to the process to the file.
|
||||
*
|
||||
* @param filename The file that the process is trying to access
|
||||
* @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.
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @param pi: The process information
|
||||
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
|
||||
*/
|
||||
int set_temp_access(const char *filename, struct process_info pi,
|
||||
set_mode_t mode);
|
||||
int give_temp_access(const char *filename, struct process_info pi);
|
||||
|
||||
#endif // !TEMP_PERMISSIONS_TABLE_H
|
||||
|
338
src/ui-socket.c
338
src/ui-socket.c
@ -6,19 +6,13 @@
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
#include "access_t.h"
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#define _GNU_SOURCE // Required for certain POSIX extensions
|
||||
#include "cc.h"
|
||||
#define _GNU_SOURCE
|
||||
#include "perm_permissions_table.h"
|
||||
#include "real_filename.h"
|
||||
#include "sourcefs.h"
|
||||
#include "temp_permissions_table.h"
|
||||
#include "ui-socket.h"
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -26,50 +20,26 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <wait.h>
|
||||
|
||||
// Exit status codes for icfs_dialogue process interaction
|
||||
#define DIALOGUE_YES 1
|
||||
#define DIALOGUE_NO 0
|
||||
#define DIALOGUE_PERM 2
|
||||
#define DIALOGUE_TEMP 0 // Bitmask position, not value
|
||||
#define ZENITY_TEMP_ALLOW_MESSAGE "Allow this time\n"
|
||||
|
||||
// Mutex to protect concurrent access to permission tables
|
||||
pthread_mutex_t access_check_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
// Structure to hold user permission decision response
|
||||
struct dialogue_response {
|
||||
access_t decision;
|
||||
char *filename;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize UI socket and required permission tables
|
||||
*
|
||||
* @param perm_permissions_db_filename Path to persistent permissions DB
|
||||
* @return 0 on success, 1 on failure
|
||||
*/
|
||||
int init_ui_socket(const char *perm_permissions_db_filename) {
|
||||
FILE *fp = NULL;
|
||||
|
||||
// Initialize in-memory temporary permissions table
|
||||
if (init_temp_permissions_table()) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] Could not initialize temporary permissions table.\n");
|
||||
fprintf(stderr, "Could not initialize temporary permissions table.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize permanent permissions database
|
||||
if (init_perm_permissions_table(perm_permissions_db_filename)) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] Could not initialize permanent permissions table.\n");
|
||||
fprintf(stderr, "Could not initialize permanent permissions table.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Verify dialogue utility is available
|
||||
fp = popen("icfs_dialogue --version", "r");
|
||||
// Test if Zenity is installed (get version)
|
||||
fp = popen("zenity --version", "r");
|
||||
if (fp == NULL) {
|
||||
perror("[ICFS] Pipe returned an error");
|
||||
perror("Pipe returned an error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -77,279 +47,107 @@ int init_ui_socket(const char *perm_permissions_db_filename) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up UI socket resources
|
||||
*/
|
||||
void destroy_ui_socket(void) {
|
||||
destroy_temp_permissions_table();
|
||||
destroy_perm_permissions_table();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query user for file access permission through GUI dialogue
|
||||
* Asks the user if the process should be allowed to access the file using the
|
||||
* GUI
|
||||
*
|
||||
* Constructs and executes the icfs_dialogue command to get user consent.
|
||||
* Handles memory allocation errors gracefully and parses the response.
|
||||
*
|
||||
* @param filename File being accessed
|
||||
* @param proc_info Process requesting access
|
||||
* @return Struct containing access decision and resolved filename
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @param pi: The process information
|
||||
* @return: 0 if access is denied, 1 if access is allowed, 2 if access is
|
||||
* allowed for the runtime of the process
|
||||
*/
|
||||
struct dialogue_response ask_access(const char *filename,
|
||||
struct process_info proc_info) {
|
||||
int ask_access(const char *filename, struct process_info proc_info) {
|
||||
FILE *fp = NULL;
|
||||
size_t command_len =
|
||||
139 + sizeof(pid_t) * 8 + strlen(proc_info.name) + strlen(filename);
|
||||
char *command = (char *)malloc(command_len);
|
||||
snprintf(command, command_len,
|
||||
"zenity --question --extra-button \"Allow this time\" --title "
|
||||
"\"Allow Access?\" --text \"Allow process "
|
||||
"<tt>%s</tt> with PID <tt>%d</tt> to access <tt>%s</tt>\"",
|
||||
proc_info.name, proc_info.PID, filename);
|
||||
|
||||
struct dialogue_response response;
|
||||
response.decision = DENY;
|
||||
response.filename = NULL;
|
||||
|
||||
// instead of popeen --------------
|
||||
|
||||
char pid_str[sizeof(pid_t) *
|
||||
8]; // amount of bits should be enough for a decimal
|
||||
snprintf(pid_str, sizeof(pid_str), "%d", proc_info.PID);
|
||||
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) == -1) {
|
||||
perror("[ICFS] pipe returned a error");
|
||||
response.filename = malloc(2);
|
||||
response.filename[0] = '/';
|
||||
response.filename[1] = 0;
|
||||
return response;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
perror("[ICFS] fork returned a error");
|
||||
response.filename = malloc(2);
|
||||
response.filename[0] = '/';
|
||||
response.filename[1] = 0;
|
||||
return response;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process
|
||||
close(pipefd[0]); // Close read end
|
||||
dup2(pipefd[1], STDOUT_FILENO); // Redirect stdout
|
||||
close(pipefd[1]); // Close original write end
|
||||
|
||||
// Prepare command and arguments
|
||||
char *args[] = {"icfs_dialogue", // Command name (looked up in PATH)
|
||||
pid_str,
|
||||
(char *)proc_info.name,
|
||||
(char *)get_mountpoint(),
|
||||
(char *)filename,
|
||||
NULL};
|
||||
|
||||
// Execute the command using execvp (uses PATH)
|
||||
execvp("icfs_dialogue", args);
|
||||
|
||||
// If execvp fails
|
||||
perror("execvp failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// instead of popen ---------------
|
||||
|
||||
close(pipefd[1]); // Close write end
|
||||
fp = fdopen(pipefd[0], "r");
|
||||
// Zenity Question Message Popup
|
||||
fp = popen(command, "r");
|
||||
free(command);
|
||||
|
||||
if (fp == NULL) {
|
||||
perror("[ICFS] fdopen returned a error");
|
||||
response.filename = malloc(2);
|
||||
response.filename[0] = '/';
|
||||
response.filename[1] = 0;
|
||||
return response;
|
||||
perror("Pipe returned a error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
str(char) dialogue_output;
|
||||
init(&dialogue_output);
|
||||
|
||||
char line[1024];
|
||||
|
||||
// Read entire command output
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
if (push_fmt(&dialogue_output, line) == NULL) {
|
||||
cleanup(&dialogue_output);
|
||||
perror("[ICFS] not enough memory for dialogue output.");
|
||||
// kill the dialogue if it's still there
|
||||
kill(pid, SIGQUIT);
|
||||
fclose(fp);
|
||||
response.filename = malloc(2);
|
||||
response.filename[0] = '/';
|
||||
response.filename[1] = 0;
|
||||
return response;
|
||||
// if the user clicks the "Allow this time" button, `zenity` will only
|
||||
// write it to `stdout`, but the exit code will still be `1`. So, we need
|
||||
// to manually check the output.
|
||||
char buffer[sizeof(ZENITY_TEMP_ALLOW_MESSAGE) + 1];
|
||||
while (fgets(buffer, sizeof(buffer), fp)) {
|
||||
printf("%s", buffer);
|
||||
if (strcmp(buffer, ZENITY_TEMP_ALLOW_MESSAGE) == 0) {
|
||||
pclose(fp);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
// Wait for the child to finish
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) == -1) {
|
||||
cleanup(&dialogue_output);
|
||||
perror("[ICFS] waitpid error");
|
||||
// kill the dialogue if it is still there
|
||||
kill(pid, SIGQUIT);
|
||||
response.filename = malloc(2);
|
||||
response.filename[0] = '/';
|
||||
response.filename[1] = 0;
|
||||
return response;
|
||||
int zenity_exit_code = WEXITSTATUS(pclose(fp));
|
||||
fprintf(stderr, "zenity returned %d\n", zenity_exit_code);
|
||||
// zenity returns 1 on "No" >:(
|
||||
if (zenity_exit_code == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dialogue_exit_code = WEXITSTATUS(status);
|
||||
|
||||
fprintf(stderr, "[ICFS] dialogue wrote out %s\n", first(&dialogue_output));
|
||||
fprintf(stderr, "[ICFS] dialogue returned %d\n", dialogue_exit_code);
|
||||
|
||||
// Handle empty output case
|
||||
if (size(&dialogue_output) == 0) {
|
||||
perror("[ICFS] empty dialogue output.");
|
||||
push(&dialogue_output, '/');
|
||||
}
|
||||
|
||||
// Allocate and copy final filename
|
||||
response.filename = malloc(size(&dialogue_output) + 1);
|
||||
strcpy(response.filename, first(&dialogue_output));
|
||||
cleanup(&dialogue_output);
|
||||
|
||||
// Parse exit code combination
|
||||
if (dialogue_exit_code == (DIALOGUE_YES | DIALOGUE_PERM)) {
|
||||
response.decision = ALLOW;
|
||||
} 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;
|
||||
} else {
|
||||
response.decision = DENY_TEMP;
|
||||
}
|
||||
|
||||
return response;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine file access based on permission tables and user input
|
||||
* Check access according to:
|
||||
* 1. temp permission table
|
||||
* 2. permanent permission table
|
||||
* 3. user descision
|
||||
*
|
||||
* Checks permissions in order:
|
||||
* 1. Temporary permission table
|
||||
* 2. Permanent permission table
|
||||
* 3. User decision via GUI
|
||||
*
|
||||
* @param filename File being accessed
|
||||
* @param pi Process information
|
||||
* @param opts Flags to force permission (GRANT_TEMP/GRANT_PERM)
|
||||
* @return 1 if access allowed, 0 if denied
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram 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);
|
||||
|
||||
// First check temporary permissions
|
||||
access_t access = check_temp_access(real_path, proc_info);
|
||||
if (access == ALLOW) {
|
||||
fprintf(stderr, "[ICFS] Permission allowed to %s based on temp table.\n",
|
||||
proc_info.name);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
if (check_temp_access(filename, proc_info) ||
|
||||
check_perm_access(filename, proc_info)) {
|
||||
// access was already granted before
|
||||
return 1;
|
||||
}
|
||||
if (access == DENY) {
|
||||
fprintf(stderr, "[ICFS] Permission denied to %s based on temp table.\n",
|
||||
proc_info.name);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Then check permanent permissions
|
||||
access = check_perm_access(real_path, proc_info);
|
||||
if (access == ALLOW) {
|
||||
fprintf(stderr, "[ICFS] Permission allowed to %s based on perm table.\n",
|
||||
proc_info.name);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
return 1;
|
||||
}
|
||||
if (access == DENY) {
|
||||
fprintf(stderr, "[ICFS] Permission denied to %s based on perm table.\n",
|
||||
proc_info.name);
|
||||
free(real_path);
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
return 0;
|
||||
}
|
||||
// if noth GRANT_TEMP and GRANT_PERM are selected, then only permanent
|
||||
// permissions are granted
|
||||
|
||||
// Handle forced permission grants
|
||||
if (opts & GRANT_PERM) {
|
||||
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);
|
||||
give_perm_access(filename, proc_info);
|
||||
return 1;
|
||||
}
|
||||
if (opts & GRANT_TEMP) {
|
||||
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);
|
||||
give_temp_access(filename, proc_info);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get user decision
|
||||
struct dialogue_response response = ask_access(filename, proc_info);
|
||||
|
||||
// Validate returned filename meets requirements:
|
||||
// 1. Must exist
|
||||
// 2. Must be prefix of original filename with trailing slash
|
||||
// or exact match
|
||||
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))))) {
|
||||
fprintf(stderr, "[ICFS] Invalid filename returned by dialogue: %s\n",
|
||||
response.filename);
|
||||
free(response.filename);
|
||||
response = ask_access(filename, proc_info);
|
||||
int user_response = ask_access(filename, proc_info);
|
||||
if (user_response == 1) {
|
||||
// user said "yes"
|
||||
give_perm_access(filename, proc_info);
|
||||
return 1;
|
||||
}
|
||||
|
||||
free(real_path);
|
||||
real_path = real_filename(response.filename);
|
||||
free(response.filename);
|
||||
|
||||
int ret = 0;
|
||||
|
||||
// Apply user decision to appropriate permission table
|
||||
if (response.decision == ALLOW) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] Permission granted permanently to %s based on response.\n",
|
||||
proc_info.name);
|
||||
set_perm_access(real_path, proc_info, SET_ALLOW);
|
||||
ret = 1;
|
||||
} else if (response.decision == ALLOW_TEMP) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] Permission granted temporarily to %s based on response.\n",
|
||||
proc_info.name);
|
||||
set_temp_access(real_path, proc_info, SET_ALLOW);
|
||||
ret = 1;
|
||||
} else if (response.decision == DENY_TEMP) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] Permission denied temporarily to %s based on response.\n",
|
||||
proc_info.name);
|
||||
set_temp_access(real_path, proc_info, SET_DENY);
|
||||
ret = 0;
|
||||
} else if (response.decision == DENY) {
|
||||
fprintf(stderr,
|
||||
"[ICFS] Permission denied permanently to %s based on response.\n",
|
||||
proc_info.name);
|
||||
set_perm_access(real_path, proc_info, SET_DENY);
|
||||
ret = 0;
|
||||
if (user_response == 2) {
|
||||
// user said "yes, but only this time"
|
||||
give_temp_access(filename, proc_info);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&access_check_mutex);
|
||||
free(real_path);
|
||||
return ret;
|
||||
// otherwise "no"
|
||||
return 0;
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
See the file LICENSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Interface for controlling communication with the UI.
|
||||
*/
|
||||
|
||||
#ifndef UI_SOCKET_H
|
||||
#define UI_SOCKET_H
|
||||
|
||||
@ -13,30 +17,27 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
/**
|
||||
* Initialize UI socket and required permission tables
|
||||
* Initialize the GUI communication.
|
||||
*
|
||||
* @param perm_permissions_db_filename Path to persistent permissions DB
|
||||
* @return 0 on success, 1 on failure
|
||||
* @return: 0 on success, -1 on faliure.
|
||||
*/
|
||||
int init_ui_socket(const char *perm_permissions_db_filename);
|
||||
|
||||
/**
|
||||
* Clean up UI socket resources
|
||||
* Close the GUI communication.
|
||||
*/
|
||||
void destroy_ui_socket(void);
|
||||
|
||||
/**
|
||||
* Determine file access based on permission tables and user input
|
||||
* Check access according to:
|
||||
* 1. temporary permission table
|
||||
* 2. permanent permission table
|
||||
* 3. user descision
|
||||
*
|
||||
* Checks permissions in order:
|
||||
* 1. Temporary permission table
|
||||
* 2. Permanent permission table
|
||||
* 3. User decision via GUI
|
||||
*
|
||||
* @param filename File being accessed
|
||||
* @param pi Process information
|
||||
* @param opts Flags to force permission (GRANT_TEMP/GRANT_PERM)
|
||||
* @return 1 if access allowed, 0 if denied
|
||||
* @param filename: The file that the process is trying to access
|
||||
* @pram 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 pi, int opts);
|
||||
|
||||
|
@ -1,42 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# fake-icfs_dialogue: script that mocks the behavior of icfs_dialogue based on the ./.fake-icfs_dialogue-response file
|
||||
|
||||
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 :)
|
||||
echo "$2" >~/.fake_icfs_dialogue_response
|
||||
elif [[ $1 == "--set-fake-response-filename" ]]; then
|
||||
echo "$2" >~/.fake_icfs_dialogue_response_filename
|
||||
elif [[ $1 == "--reset-fake-response" ]]; then
|
||||
rm ~/.fake_icfs_dialogue_response ~/.fake_icfs_dialogue_response_filename
|
||||
else
|
||||
if [ -f ~/.fake_icfs_dialogue_response ]; then
|
||||
FAKE_ICFS_DIALOGUE_RESPONSE=$(cat ~/.fake_icfs_dialogue_response)
|
||||
|
||||
if [[ -f ~/.fake_icfs_dialogue_response_filename ]]; then
|
||||
FAKE_ICFS_DIALOGUE_RESPONSE_FILENAME=$(cat ~/.fake_icfs_dialogue_response_filename)
|
||||
if [[ $FAKE_ICFS_DIALOGUE_RESPONSE_FILENAME == "" ]]; then
|
||||
printf "%s" "$4"
|
||||
else
|
||||
printf "%s" "$(cat ~/.fake_icfs_dialogue_response_filename)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "yes" ]]; then
|
||||
exit "$((ICFS_DIALOGUE_YES | ICFS_DIALOGUE_TEMP))"
|
||||
elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "no" ]]; then
|
||||
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
|
||||
exit "$((ICFS_DIALOGUE_NO | ICFS_DIALOGUE_PERM))"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 255 # TODO: call actual icfs_dialogue here
|
30
test/mock/zenity
Executable file
30
test/mock/zenity
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# fake-zenity: script that mocks the behavior of zenity based on the ./.fake-zenity-response file
|
||||
|
||||
if [[ $1 == "--set-fake-response" ]]; then
|
||||
#someone knows we are fake :)
|
||||
echo $2 >~/.fake_zenity_response
|
||||
else
|
||||
if [ -f ~/.fake_zenity_response ]; then
|
||||
FAKE_ZENITY_RESPONSE=$(cat ~/.fake_zenity_response)
|
||||
|
||||
if [[ $FAKE_ZENITY_RESPONSE == "yes_tmp" ]]; then
|
||||
printf "Allow this time\n"
|
||||
exit 1
|
||||
elif [[ $FAKE_ZENITY_RESPONSE == "yes_tmp_alt" ]]; then
|
||||
printf "Allow this time\n"
|
||||
echo "yes_alt" >~/.fake_zenity_response
|
||||
exit 1
|
||||
elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then
|
||||
exit 1
|
||||
elif [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then
|
||||
exit 0
|
||||
elif [[ $FAKE_ZENITY_RESPONSE == "yes_alt" ]]; then
|
||||
echo "yes_tmp_alt" >~/.fake_zenity_response
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 255 # TODO: call actual zenity here
|
@ -1,81 +0,0 @@
|
||||
SHELL=/bin/bash
|
||||
|
||||
# configurable options
|
||||
|
||||
ifndef ($(SOURCES_DIR))
|
||||
SOURCES_DIR := .
|
||||
endif
|
||||
|
||||
ifndef ($(TESTS_DIR))
|
||||
TESTS_DIR := .
|
||||
endif
|
||||
|
||||
ifndef ($(BUILD_DIR))
|
||||
BUILD_DIR := .
|
||||
endif
|
||||
|
||||
CC := gcc
|
||||
CXX := g++
|
||||
|
||||
NAME := opener
|
||||
|
||||
# dependencies
|
||||
|
||||
PACKAGE_NAMES :=
|
||||
|
||||
ifeq ($(TEST), 1)
|
||||
# PACKAGE_NAMES += check # TODO: use check?
|
||||
endif
|
||||
|
||||
|
||||
# set up cflags and libs
|
||||
|
||||
CFLAGS :=
|
||||
LDFLAGS :=
|
||||
|
||||
ifneq ($(PACKAGE_NAMES),)
|
||||
CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES))
|
||||
LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES))
|
||||
endif
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
CFLAGS += -O0 -pedantic -g -Wall -Wextra -Wcast-align \
|
||||
-Wcast-qual -Wdisabled-optimization -Wformat=2 \
|
||||
-Winit-self -Wlogical-op -Wmissing-declarations \
|
||||
-Wmissing-include-dirs -Wredundant-decls -Wshadow \
|
||||
-Wsign-conversion -Wstrict-overflow=5 \
|
||||
-Wswitch-default -Wundef -Wno-unused
|
||||
LDFLAGS +=
|
||||
else
|
||||
CFLAGS += -O3
|
||||
LDFLAGS +=
|
||||
endif
|
||||
|
||||
|
||||
# set up targets
|
||||
|
||||
TARGETS := $(BUILD_DIR)/$(NAME)
|
||||
|
||||
ifeq ($(TEST), 1)
|
||||
TARGETS += $(NAME)_test
|
||||
endif
|
||||
|
||||
|
||||
# build!
|
||||
|
||||
default: $(TARGETS)
|
||||
|
||||
.PHONY: clean $(NAME)_test
|
||||
|
||||
$(NAME)_test: $(BUILD_DIR)/$(NAME)
|
||||
echo "No tests defined."
|
||||
#$(BUILD_DIR)/$(NAME)
|
||||
|
||||
$(BUILD_DIR)/$(NAME): $(BUILD_DIR)/$(NAME).o
|
||||
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/$(NAME)
|
||||
|
||||
$(BUILD_DIR)/$(NAME).o: $(SOURCES_DIR)/$(NAME).c
|
||||
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $(BUILD_DIR)/$(NAME).o
|
||||
|
||||
clean:
|
||||
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/$(NAME)
|
@ -1,94 +0,0 @@
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define PATH_MAX 4096
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Check for correct usage
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *path = argv[1];
|
||||
struct stat statbuf;
|
||||
|
||||
// Stat the given path to determine if it's a directory
|
||||
if (lstat(path, &statbuf) == -1) {
|
||||
perror("lstat");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Case 1: The path is not a directory
|
||||
if (!S_ISDIR(statbuf.st_mode)) {
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
perror("open");
|
||||
return 1;
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Case 2: The path is a directory
|
||||
DIR *dirp = opendir(path);
|
||||
if (dirp == NULL) {
|
||||
perror("opendir");
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct dirent *entry;
|
||||
int success = 1;
|
||||
|
||||
while ((entry = readdir(dirp)) != NULL) {
|
||||
// Skip . and ..
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Construct the full path
|
||||
char *fullpath = NULL;
|
||||
if (asprintf(&fullpath, "%s/%s", path, entry->d_name) == -1 ||
|
||||
fullpath == NULL) {
|
||||
perror("asprintf");
|
||||
success = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Stat the entry to check if it's a regular file
|
||||
struct stat entry_stat;
|
||||
if (lstat(fullpath, &entry_stat) == -1) {
|
||||
perror("lstat");
|
||||
success = 0;
|
||||
free(fullpath);
|
||||
break;
|
||||
}
|
||||
|
||||
// Only process regular files
|
||||
if (!S_ISREG(entry_stat.st_mode)) {
|
||||
free(fullpath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to open and immediately close the file
|
||||
int fd = open(fullpath, O_RDONLY);
|
||||
free(fullpath);
|
||||
if (fd == -1) {
|
||||
perror("open");
|
||||
success = 0;
|
||||
break;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
closedir(dirp);
|
||||
|
||||
return (success ? 0 : 1);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
count=$1
|
||||
for i in $(seq $count); do
|
||||
$2 ./protected/haystack/
|
||||
done
|
170
test/test.bash
170
test/test.bash
@ -9,24 +9,6 @@ touch ./protected/do-not-remove ./protected/should-be-removed ./protected/truth
|
||||
chmod 777 ./protected/perm777 ./protected/perm000
|
||||
echo "Free code, free world." >./protected/motto
|
||||
|
||||
mkdir protected/haystack
|
||||
for i in {1..100}; do
|
||||
touch "./protected/haystack/hay$i"
|
||||
done
|
||||
touch ./protected/haystack/needle
|
||||
echo "Liberty in every line." >./protected/haystack/needle
|
||||
|
||||
rm -rf ./openers
|
||||
mkdir openers
|
||||
make -C ./opener || (
|
||||
echo "Could not make the opener program."
|
||||
exit 1
|
||||
)
|
||||
for i in {1..12}; do
|
||||
cp ./opener/opener "./openers/opener$i"
|
||||
ln --symbolic "$(realpath "./openers/opener$i")" "./openers/symlinked_opener$i"
|
||||
done
|
||||
|
||||
# set up the fake-zenity
|
||||
|
||||
PATH="$(realpath ./mock/):$PATH"
|
||||
@ -43,12 +25,14 @@ 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."
|
||||
elif [[ $1 == "--perf" ]]; then
|
||||
echo "Profiling with perf..."
|
||||
../build/icfs -o default_permissions ./protected ./.pt.db &
|
||||
sleep 5
|
||||
echo "Profiling will require root privilieges."
|
||||
sleep 3
|
||||
echo "Attaching to $(pgrep icfs)"
|
||||
sudo perf record -g -e cycles:u --call-graph dwarf -p $(pgrep icfs) &
|
||||
sleep 10
|
||||
else
|
||||
echo "Database protection will not be tested due to the lack of setuid capabilites."
|
||||
echo "To test it, run this script with '--setuid'."
|
||||
@ -58,183 +42,121 @@ 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**.
|
||||
|
||||
# create files
|
||||
|
||||
icfs_dialogue --set-fake-response no
|
||||
zenity --set-fake-response no
|
||||
truncate -s 0 ./protected/should-exist-anyway 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: truncate cannot create protected/should-exist despite access being permitted!" # OK
|
||||
|
||||
icfs_dialogue --set-fake-response yes
|
||||
zenity --set-fake-response yes_tmp
|
||||
truncate -s 0 ./protected/should-exist 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: truncate cannot create protected/should-exist despite access being permitted!" # OK
|
||||
|
||||
# write to files
|
||||
|
||||
icfs_dialogue --set-fake-response no
|
||||
zenity --set-fake-response no
|
||||
sed -e 'a\'"Linux is a cancer that attaches itself in an intellectual property sense to everything it touches." "./protected/truth" 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: echo can write to protected/lie despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
|
||||
icfs_dialogue --set-fake-response yes
|
||||
zenity --set-fake-response yes_tmp
|
||||
sed -e 'a\'"Sharing knowledge is the most fundamental act of friendship. Because it is a way you can give something without loosing something." "./protected/truth" 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: echo cannot write to protected/truth despite access being permitted!" # OK
|
||||
|
||||
# Read files
|
||||
|
||||
icfs_dialogue --set-fake-response no
|
||||
zenity --set-fake-response no
|
||||
cat ./protected/motto >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: cat can read protected/this-only despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
|
||||
icfs_dialogue --set-fake-response yes
|
||||
zenity --set-fake-response yes_tmp
|
||||
cat ./protected/motto >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: echo cannot create protected/this-only despite access being permitted!" # "Free code, free world."
|
||||
|
||||
# remove files
|
||||
|
||||
icfs_dialogue --set-fake-response no
|
||||
zenity --set-fake-response no
|
||||
rm ./protected/do-not-remove >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: rm can unlink protected/do-not-remove despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
|
||||
icfs_dialogue --set-fake-response yes
|
||||
zenity --set-fake-response yes_tmp
|
||||
rm ./protected/should-be-removed >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: rm cannot unlink protected/should-be-removed despite access being permitted!" # OK
|
||||
|
||||
# rename files
|
||||
|
||||
icfs_dialogue --set-fake-response no
|
||||
zenity --set-fake-response no
|
||||
mv ./protected/do-not-rename ./protected/terrible-name 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: mv can rename protected/truth despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
icfs_dialogue --set-fake-response yes
|
||||
zenity --set-fake-response yes_tmp
|
||||
mv ./protected/should-be-renamed ./protected/great-name 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: mv cannot rename should-be-removed to renamed-file despite access being permitted!" # OK
|
||||
|
||||
# change permissions
|
||||
|
||||
icfs_dialogue --set-fake-response no
|
||||
zenity --set-fake-response no
|
||||
chmod 000 ./protected/perm777 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: chmod can change permissions of protected/perm777 despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
icfs_dialogue --set-fake-response yes
|
||||
zenity --set-fake-response yes_tmp
|
||||
chmod 000 ./protected/perm000 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: chmod cannot change permissions of protected/perm000 despite access being permitted!" # OK
|
||||
|
||||
# test permanent permissions
|
||||
|
||||
icfs_dialogue --set-fake-response yes_perm
|
||||
openers/opener1 ./protected/motto >/dev/null 2>/dev/null &&
|
||||
zenity --set-fake-response yes
|
||||
cat ./protected/motto >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: openers/opener1 cannot read protected/motto despite access being permitted!" # OK
|
||||
echo "[ICFS-TEST]: echo cannot read protected/motto despite access being permitted!" # OK
|
||||
|
||||
icfs_dialogue --set-fake-response no # this should be ignored
|
||||
openers/opener1 ./protected/motto >/dev/null 2>/dev/null &&
|
||||
zenity --set-fake-response no # this should be ignored
|
||||
cat ./protected/motto >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: openers/opener1 cannot read protected/motto despite access being permitted!" # OK
|
||||
|
||||
icfs_dialogue --set-fake-response no # this should be ignored
|
||||
openers/symlinked_opener1 ./protected/motto >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: openers/symlinked_opener1 cannot read protected/motto despite access being permitted!" # OK
|
||||
|
||||
icfs_dialogue --set-fake-response no_perm
|
||||
openers/opener2 ./protected/motto >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: openers/opener2 can read protected/motto despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
|
||||
icfs_dialogue --set-fake-response yes # this should be ignored
|
||||
openers/opener2 ./protected/motto >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: openers/opener2 can read protected/motto despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
|
||||
icfs_dialogue --set-fake-response yes # this should be ignored
|
||||
openers/symlinked_opener2 ./protected/motto >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: openers/symlinked_opener2 can read protected/motto despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
|
||||
# test permission globbing
|
||||
|
||||
icfs_dialogue --set-fake-response yes
|
||||
icfs_dialogue --set-fake-response-filename "/"
|
||||
openers/opener3 ./protected/haystack >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: openers/opener3 cannot read protected/haystack/needle despite access being permitted!" # OK
|
||||
|
||||
icfs_dialogue --set-fake-response no
|
||||
icfs_dialogue --set-fake-response-filename "/"
|
||||
openers/opener4 ./protected/haystack >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: openers/opener4 can read files in protected/haystack despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
|
||||
icfs_dialogue --set-fake-response yes_perm
|
||||
icfs_dialogue --set-fake-response-filename "/"
|
||||
openers/opener5 ./protected/haystack/needle >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: openers/opener5 cannot read protected/haystack/needle despite access being permitted!" # OK
|
||||
|
||||
icfs_dialogue --set-fake-response no # this should be ignored
|
||||
openers/opener5 ./protected/haystack >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: OK" ||
|
||||
echo "[ICFS-TEST]: openers/opener5 cannot read files in protected/haystack despite access being permitted!" # OK
|
||||
|
||||
icfs_dialogue --set-fake-response no_perm
|
||||
icfs_dialogue --set-fake-response-filename "/"
|
||||
openers/opener6 ./protected/haystack/needle >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: openers/opener6 can read protected/haystack/needle despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
|
||||
icfs_dialogue --set-fake-response yes # this should be ignored
|
||||
openers/opener6 ./protected/haystack >/dev/null 2>/dev/null &&
|
||||
echo "[ICFS-TEST]: openers/opener6 can read files in protected/haystack despite access being denied!" ||
|
||||
echo "[ICFS-TEST]: OK" # EACCESS
|
||||
echo "[ICFS-TEST]: echo cannot read protected/motto despite access being permitted!" # OK
|
||||
|
||||
# test database access
|
||||
if [[ $1 == '--setuid' ]]; then
|
||||
if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then
|
||||
echo "[ICFS-TEST]: permanent permissions database is accessible!"
|
||||
else
|
||||
echo "[ICFS-TEST]: OK"
|
||||
fi
|
||||
if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then
|
||||
echo "[ICFS-TEST]: permanent permissions is accessible!"
|
||||
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"
|
||||
echo "[ICFS-TEST]: OK"
|
||||
fi
|
||||
|
||||
# performance testing code
|
||||
|
||||
RUNS_NUM=500
|
||||
if [[ $1 == '--performance' ]]; then
|
||||
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"
|
||||
if [[ $1 == "--perf" ]]; then
|
||||
zenity --set-fake-response yes_tmp
|
||||
rm -rf ./protected/*
|
||||
zenity --set-fake-response yes_alt
|
||||
bonnie++ -p 4
|
||||
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null &
|
||||
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null &
|
||||
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null &
|
||||
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null
|
||||
bonnie++ -p -1
|
||||
fi
|
||||
|
||||
# unmount
|
||||
|
||||
sleep 0.5
|
||||
#lsof +f -- $(realpath ./protected)
|
||||
umount "$(realpath ./protected)"
|
||||
sleep 3
|
||||
umount $(realpath ./protected)
|
||||
sleep 0.5
|
||||
|
||||
# test the same thing, but without ICFS mounted
|
||||
|
||||
if [[ $1 == '--performance' ]]; then
|
||||
echo "[ICFS-TEST]: bare filesystem"
|
||||
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener9"
|
||||
if [[ $1 == "--perf" ]]; then
|
||||
mv ./callgraph.png ./callgraph_old.png
|
||||
real_user=$USER
|
||||
sudo chown "$real_user" ./perf.data
|
||||
perf script --dsos=icfs | gprof2dot -f perf | dot -Tpng -o callgraph.png
|
||||
echo "Profile graph was written to \"callgraph.png\""
|
||||
fi
|
||||
|
Loading…
x
Reference in New Issue
Block a user