Limiting the power of package installation in Debian
lwn.net
lwn.net
IMHO, a package should deliver a set of files to certain directories. That's it.
It should not overwrite existing files, that were installed by other packages. It should not change existing files in any way.
It might advise the system to trigger certain reindexing actions (systemd daemon reolad, update man-db, etc.) but doing this should be the duty of the package manager, not the package itself.
AFAIK, nix and Solaris' pkg are pretty close to this ideal.
A big advantage that this has, on top of security, is that:
- packages can be uninstalled safely and without side-effects
- package contents can be inspected (pkg contents)
- corrupted installations can be detected using checksums (pkg fix)
- package updates/installs can be rolled back using file system snapshots.
You can, if the community wants to go in that direction.
Ideally the software which loads "plugins.xml" is updated to support a conf.d-like directory full of "plugin-pkg1.xml" through "plugin-pkgN.xml", each owned by separate packages. Many OSS projects have done this.
If that's not possible (closed-source, too much work, whatever) then it seems simple enough to build a separate XML-merging tool that the package manager invokes which combines the separate XML files during package installation.
Honestly: the defence of shit software and user-hostile developers never ceases to amaze me.
Which is fine and all, but don't expect users to flock to your distribution that doesn't support $SOFTWARE because you decided they do things wrong.
It's generally a good practice to make your system open for extension but closed to modifications [1].
There is no reason, the user database can not work the same way. Just have the system scan `/etc/passwd.d/*` on boot/reload and drop files there.
[1] https://drive.google.com/file/d/0BwhCYaYDn8EgODUxZTJhOWEtMTZ...
Schema migrations would still need a script or something though.
This need not be an automatic action, though; you might choose to postpone the migration, and offer a separate tool for the user to run.
This prevents any packages from depending on such a package, though: they can no longer be automatically installed, because their dependency cannot, too.
This changes the concept of package management seriously enough.
But, unfortunately, some software does not, and it still needs to be packaged.
* If the package owns the file, and the user is not supposed to make changes to it, then package updates may do whatever to the file on update. It's good practice though, to leave a copy of the old file in /lost+found.
* If the user is allowed to make modification, then the user owns it. The configuration syntax becomes and API and should be treated as such. Breaking changes should be rare, preceded by a deprecation period and announced as major versions. As you say, it's questionable if such updates can/should be automated.
Mutable, persistent state is usually put under /var and is user-owned. Package updates should not touch data in there.
Any admin can view the contents to debug or spot any potential security issues.
That’s because you probably never built a package management system. If you limit the package management system to the actions you propose it’s only usable for iOS type apps and that could be useful but then you still need a solution for managing the rest of the system.
In Nix postInstall (and preInstall, as well as preBuild/postBuild, etc) specifying commands to execute before/after the corresponding "phase" -- so if a package is "almost" good to go with just "make install", you could use postInstall to do something like copy a file omitted by upstream's installation target.
The point is that postInstall in Nix is part of how the package itself is constructed-- in contrast to commands run after installing the package. There is no equivalent for this in Nix in a fundamental way (not by policy or for technical reasons).
If you're unsure about bugs in a package's install script, why aren't you equally unsure about bugs in the binaries installed by the package.
In-fact, install scripts are auditable; third party compiled binaries aren't (at least not easily).
I see other advantages in declarative approaches - for example more freedom for debian to change the underlying file system layout, or to give the user some information about what the package is going to change for easier troubleshooting, but I do not see any advantage security-wise.
Like, the idea that I care that Chromium can run software as root is nonsensical: yes, root can modify all of the software not on my computer... to what end? The only thing of value on my computer is in my home directory, owned by me... hell, thanks to a bunch of people who (incorrectly) think they can make their computers safer by running fewer things as root, a ton of executable files are in my home directory thanks to userspace package managers provided by rust and node.js, so you can even modify other software without even having to be root anymore :/. There is simply no security advantage to any of this.
There is one other point: if my OS is sound and my user files are corrupted - well at least I can restore my files from backup without first trying to reinstall my machine. It saves a little effort.
You can have multiple user accounts, they don't need to be associated 1:1 with living people.
And blanket warning about such packages would lead to a slew of false positives and warning fatigue (heck, even ping needs to be suid root)
Then there's things like Flatpak. They also want to make it easy to package and distribute software. And they see all the features of Docker and go, "Hey, a sandbox! That sounds cool! Let's make it mandatory!" In order to simply distribute software in a compatible way, they include a lot of restrictions they don't need to just distribute and run software.
All you need to distribute a software package is files, and a subsystem that maps files into the user's environment, and links together the files needed to run the software. We can accomplish this with a copy-on-write, overlay filesystem, and some software to download dependent files and lay them out in the right way. It should be incredibly simple, and it should work on any operating system that supports those filesystems. And those filesystems should be simple enough to implement on any operating system!
So what the hell is the deal here? Why has nobody come along and just provided the bare minimum needed to just distribute software (edit: in a way that also allows it to be run with all its dependencies in one overlay filesystem view)? Why is it always some ass-backwards incompatible crap that is "controversial" ? Why can't we just make something that works for any software?
You're welcome.
> All you need to distribute a software package is files, and a subsystem that maps files into the user's environment, and links together the files needed to run the software. We can accomplish this with a copy-on-write, overlay filesystem, and some software to download dependent files and lay them out in the right way. It should be incredibly simple, and it should work on any operating system that supports those filesystems. And those filesystems should be simple enough to implement on any operating system!
It's literally, and exactly that. I'm not sure how it can be not what you're looking for.
Also, you may be missing that I'm looking for Docker-like functionality. That is to say, have, say, 3 trees of files, one built on top of the other. When launching the application, lay the first tree down on an overlay, then the second, then the third, then run the application. By having the third tree link to the first two, I just pick the tree I want to run (the third tree, the one with the app), and it creates the correct overlays with the correct dependencies and runs it. No special paths needed by the application. What I'm asking for could be done with existing packaged software, with no need to change existing packages - the same way Docker does it now.
I'll summarize it here:
1. To use it to run application: a. Get AppFSd running; then b. Run the application you actually wanted to run 2. To package an application: a. Create a package manifest b. Run the build script to create a CPIO archive c. Upload that CPIO archive to a webserver d. Run the script to publish the archive 3. The package manifest format is described in the README ( http://appfs.rkeene.org/web/doc/trunk/README.md ) -- it's CSV with the format "type,time,extraData,name" where the extraData is depends on the value of "type" for type==file, it's "size,perms,sha1"
If you're looking for something Docker-like then it's different since this is a filesystem based approach which doesn't require any of the containerization techniques used by Docker... which is how this conversation got started. It would also not be available on every platform since not every platform supports the same containerization mechanisms, and for platforms that do they often require escalated privileges.
The Overlay2 filesystem driver is Linux-native, but you could implement it as a FUSE module. It would provide basically all the functionality I'm looking for, minus the network code. The idea would be to unpack software packages on the filesystem (e.g. chroot environment) and then overlay the directories of the packages needed before running a particular version of an app.
In order to make custom paths transparent to the application, you need some custom system calls that Linux provides, such as mount namespaces, which is essentially a containerization technology - but you don't need to use "containers" per se, just a particular system call. If you don't use mount namespaces, you have to use complicated hacks to make an application's view of the filesystem unique, such as chroot with bind mounts, or an LD_PRELOAD filter, but all that's too hacky for a general solution.
Plan9 had mount namespaces (among other things) decades ago, but good luck getting modern OSes to implement useful features in a standard way
a) the ass-backwards approach the company took to building a package manager.
b) the new development paradigm of "fuck it, find a library". Flying Spaghetti monster forbid these hipster developers actually have to write some fucking code themselves, its easier just to duct-tape together 200 different 'libraries', half of which do what you could do in 5 lines or less, and produces a dependency tree like fucking crab grass.
All praise his noodleness.
> Wouldn't it be neat if such an atrocity wasn't possible?
An easier way might be to make it difficult for users to install "unofficial packages", but that would be against the philosophy of users having ultimate control over their systems.
Your suggestion puts us in the rather interesting situation that you're requesting a new feature for something that primarily affects third party packages that aren't actually supported in the first place.
Postinsts and other control files should on the whole be really simple.
This post seems to be more about protecting users from accidental problems with package installation scripts, but not about deliberate problems with packages, nor accidental problems with packages post installation script.
When I take an untrusted .deb, I look inside at the files it's dumping out, and the scripts it's going to run. Making apt more declarative wouldn't stop that.
It's just like how you need software like Revo Uninstaller on Windows to actually clean up everything: why?
It might not cause you day-to-day problems, but it's a massive defect in my eyes.
I recall that episode made some noise at the time...
And then there are things that are declarative in the debian/ dir (like auto-(re)starting the installed services) that end up as generated, procedural code in the postinst script.
When such things can be done in a declarative manner, it's much easier to reason about them programmatically, and maybe you could completely disable postinst scripts for a whole category of packages.
I think an escape to bash might always be required, but I think it should be a restrictive thing. That is packages with that flag require a special flag passed to dpkg. Also any packages using bash should automatically get extra reviewers.
# dedicated user accounts
file_owning_user "cron" "/var/spool/cron"
directory "cron" "/var/spool/cron" "0755"
directory "cron" "/var/spool/cron/tmp" "0700"
directory "cron" "/var/spool/cron/crontabs" "0700"
# bcron services
service_with_dedicated_logger "bcron-start"
service_with_dedicated_logger "bcron-update"
socket_with_dedicated_logger "bcron-spool"
# user-mode TTYs
user_tty "vc1-tty"
user_tty "vc2-tty"
user_tty "vc3-tty"
service_with_dedicated_logger "terminal-emulator@vc1"
service_with_dedicated_logger "terminal-emulator@vc2"
service_with_dedicated_logger "terminal-emulator@vc3"
login_service_with_dedicated_logger "vc1-tty"
login_service_with_dedicated_logger "vc2-tty"
login_service_with_dedicated_logger "vc3-tty"
# TTY ancillaries
service_with_dedicated_logger "console-input-method@head0"
service_with_dedicated_logger "console-multiplexor@head0"
service_with_dedicated_logger "console-fb-realizer@head0"
There are different shell function mappings for pre/post-install/remove/update, making what (say) "login_service_with_dedicated_logger" does vary according to the action being taken and whether the package is a "tools" package or a "run" package. In the post-install of a "tools" package it creates dedicated user accounts and log file directories. In the post-install of a "run" package it conditionally enables and starts services.It still needed the ability to run general-purpose commands here and there, especially to perform tidying up as things were renamed and errors were corrected over the years, and the number of declarative verbs that one turns out to need in practice is quite large.