There's no silver bullets here, but we might be able to silver plate a few.
There's no silver bullets here, but we might be able to silver plate a few.
I entirely agree.
> Permissions at install time are a good idea.
I'm actually not so sure about this. I think the "iOS model" of asking for permissions when necessary is much better than the "Android model"* of a big list of permissions on install, preventing use of the app if any of them aren't granted (leading to users giving apps less scrutiny over their permissions than than they would under the iOS model).
* I believe some recent versions of Android (M?) may support the "iOS model" in some form.
Now, the real problem is that permissions are too general. The "access/read/write files" permission is all grouped up in one place, so you end up with tons of directory for every app in your root directory (that don't get deleted when uninstalling the apps that generated them), and you allow unnecessary access to other files as well. Or the network permission, which could lead to all sorts of traffic, while many developers just need it for ads.
Maybe whats needed is more of a trust model. Users could ask "what would Bruce Schneier do" for example. If Bruce [substitute trusted person of your choice] would install this app, then I'm happy to do it as well.
Of course, you have a firewall if you're rooted but I'm not rooted when on a Nexus device.
Actually it is the Symbian model.
E.g. OSes have an `open()` system call, which can potentially access any part of the filesystem, and then they layer an ever growing permission system to restrict this.
Can we design a system where there is no `open()` call at all? Instead, the program declares "I accept an image and output an image", and when it is invoked, the OS asks the user for an image, making it clear which program is asking for it. Then the OS calls the program, passing in the selected file.
This model has other advantages such as introspection, composability, validation and isolation (e.g. a program that declares it takes an image an output an image has no access to the network anyway and cannot send you image over the network.)
Alternatively there could be standard way to pass an image (or any other input) to a program - similar to a command line arg in current systems, for instance.
Yes you cannot store filenames, but you could store some other serialized token generated from a file and the token could be used to recreate the file object. Alternatively, if you have an image based system, you don't have to convert the file object to a token explicitly - you just hold the references to the file objects and they're automatically persisted and loaded.
The point of such a system would be that programs cannot explore or read the filesystem as there is no filesystem API. But programs can operate on files explicitly given to them. So exploring the filesystem is restricted to some primitives that have to be used explicitly. The guarantee then is if I invoke a program without giving it a file or folder, I know it absolutely cannot access any file.
But if I can do those things (especially the second), then that seems to open at least some attack vectors (that would obviously depend on the actual rules).
- The programs behave somewhat like classes - they define input and output 'slots' (akin to instance attributes). But they don't have access to a filesystem API (or potentially even other services, such as network). Programs can have multiple input and output slots.
- You can instantiate multiple instances of the program (just like multiple running processes for the same executable). Unlike running unix processes, instantiated programs can be persisted (and are by default) - it basically persists a reference to the program and references to the values for the input slots.
- When data is provided to the input slot of an instantiated program (lets call this data binding), the program produces output data in the output slot.
- You can build pipeline of programs by connecting the output slot of one program to the input slot of another. This is how you compose larger programs from smaller programs. This could even contain control and routing nodes so you can construct data flow graphs.
- Separately, there are some data stores, these could be filesystem style or relational or key/value.
The shell isn't a typical shell - it has the capability to compose programs and bind data. It also doesn't execute scripts at all - it can only be used interactively to compose and invoke the program graphs. A shell is bound to a data store - so it has access to the entire data store, but is only used interactively by an authenticated user.
So interactive invocation of a program may look something like this:
> /path/to/file1 | some_program | /path/to/file2
# this invokes some_program, attaches file1 to the input slot, saves the output slot contents to file2.
You could save the instantiated program itself if you want. > some_program_for_file1 = [/path/to/file1 | some_program]
Then invoke it any number of times. > some_program_for_file1 | /path/to/file3 # runs some_program on existing contents
(update file1 here...)
> some_program_for_file1 | /path/to/file4 # runs some_program on new contents
With advanced filtering programs, you could define more complex sets of input files. > /path/to/folder | filter_program(age>10d, size<1M) | some_program | /path/to/output_folder
You can even persist the instantiated query, and reuse it > interesting_files = [/path/to/folder | filter_program(age<1d)]
> interesting_files | program_one
> interesting_files | program_two
So that's the rough idea, using an ad-hoc made up syntax for single input/output slot programs.