crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.44k stars 1.62k forks source link

[RFC] Remove `File::Flags` #8026

Open didactic-drunk opened 5 years ago

didactic-drunk commented 5 years ago

Problem

The current API for dealing with setuid/setgid/sticky bits is over abstracted and missing useful features. I should be able to:

For .to_s to work the information must be contained in File::Permissions, not a separate structure.

Passing the info to chmod requires the same.

Getting the actual POSIX bits requires the same.

Setting the bits probably requires the same.

Implementation

  1. Don't strip the information from Permissions.
  2. Add File::Permissions.[setuid?, setgid?, sticky?]
  3. @[Deprecated] delegate File::Info.[setuid?, setgid?, sticky?] to permissions.
  4. Add a method to strip setuid/setgid/sticky from permissions.
  5. Remove File::Flags.

Background

A few weeks ago I was working with setuid files and found it utterly frustrating as the information is

  1. Not transferable to other files.
  2. Not available with permissions.
  3. permissions.to_s shows incorrect values by omitting setuid/setgid/sticky status.

Try copying a setgid file or directory in crystal and you'll see what I mean. What was a 3 line file copy adds 3 if statements.

  permissions |= 0o400 if file_info.setuid?
  permissions |= 0o200 if file_info.setgid?
  permissions |= 0o100 if file_info.sticky?

I found it quicker to monkey patch File::Info and work with the permissions directly.

File::Flags is pretty abstraction but not useful in the application space that uses it.

RX14 commented 5 years ago

They should not be in permissions. The conflation of flags and permissions is an unneccesary UNIXism. The design of File::Info and File::Flags is fine. You're just missing a way to set/copy File::Flags on a file.

In fact, setting suid, sgid, or sticky "by accident" is possible with your API when copying permissions - and dangerous to security.

RX14 commented 5 years ago

My preferred API would be File.chmod(path, permissions : Int) which converts the 0o655 to a File::Permissions then passes that to File.chmod(path, permissions : File::Permissions, flags : File::Flags = File::Flags::None).

didactic-drunk commented 5 years ago

So we don't have future naming collisions:

didactic-drunk commented 5 years ago

3 API's that avoid security problems while keeping setuid with permissions:

#

File.info("foo").permissions => Permissions without setuid.
# Choose a name.
File.info("foo").permissions_with_name => Permissions with `setuid`.
File.info("foo").full_permissions => Permissions with `setuid`.

Advantages:

Disadvantages:

#

permissions = File.info("foo").permissions
permissions.privileged # Sets flag indicating setuid/setgid is allowed when setting permissions.
File.chmod("bar", permissions.privileged)

Advantages:

#

permissions = File.info("foo").permissions
permissions.strip_setuid # Or another name.
File.chmod("bar", permissions.strip_setuid)
didactic-drunk commented 5 years ago

You said my API creates security holes on files. That's addressed in the prior post.

The current API creates security holes on directories by not retaining setgid and sticky bits.

didactic-drunk commented 5 years ago

My preferred API would be File.chmod(path, permissions : Int) which converts the 0o655 to a File::Permissions then passes that to File.chmod(path, permissions : File::Permissions, flags : File::Flags = File::Flags::None).

My preferred API would be:

# Creates a setuid file because that's exactly what I specified.
File.chmod(path, 0o4755) 
# Now we can argue about implementation since permissions possibly
# came from some other API.
File.chmod(path, permissions)

Sometimes I need to import or export permissions to or from other file formats or programs. The current API makes this impossible (using Permissions). The proposed API makes it a PITA.

Whether or not it setuid was conflated or is inherent to permissions is besides the point. They're always together in every OS that supports them.

Perhaps you have a use case that illustrates where this is beneficial and generates fewer bugs or fewer lines of code. I documented one of my use cases under "Background" where the current API doesn't work. Another program where crystal failed is a tripwire like program I attempted to port a month or 2 ago but couldn't because readlink among others was missing. The proposed API won't work for that either.

Splitting permissions is a hindrance in actual use. I've avoided the crystal API for this reason.

RX14 commented 5 years ago

They're always together in every OS that supports them

that's only because there's only one (family of) OS which supports them

it's not about fewer lines of code, it's about reducing the amount of breaking changes (removing File::Flags) needed to support what is an uncommon usecase (setting non-permission bits).

didactic-drunk commented 5 years ago

uncommon usecase (setting non-permission bits).

Or comparing, or exporting, or importing. I admit they are all uncommon but it's not a single case. Every case of attempting to use them is made harder by splitting flags from permissions.

Where is the tangible benefit?

didactic-drunk commented 5 years ago

By splitting them permissions.to_s shows incorrect file permissions.

didactic-drunk commented 5 years ago

I see your point that maybe POSIX flags could be it's own thing with it's own API and designed differently but it's not. It's always part of the file mode.

Even if you come up with a solution it's:

If you were doing an OS abstraction maybe there would be a win but this is a POSIX only feature.

RX14 commented 5 years ago

@didactic-drunk we still need to do OS abstraction, especially because it's a POSIX-only feature.

I'd like others to chip in, but I'm unlikely to change my mind.

didactic-drunk commented 5 years ago

How would you do an OS abstraction?

How would I do an OS abstraction?

You still haven't given a tangible benefit. Or plans to fix '.to_s'. The first thing I thought when I saw the output was "How did my files lose setuid?".

didactic-drunk commented 5 years ago

For a tangible benefit what is the interface and implementation and how is it more robust?

I gave three ways to handle the issue with less code and very robust with [POSIX, Windows, 3rd party library and existing programmer expectations] compatibility.