fix(auth): add Windows session storage bridge
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
//go:build !windows
|
||||
|
||||
package safeio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func claimCanonicalPrivateRegular(source, claim string) (bool, error) {
|
||||
directory, sourceName, err := openCanonicalParentDirectory(source)
|
||||
if err != nil {
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
defer unix.Close(directory)
|
||||
claimName := claim[len(claim)-len(claimBaseName(claim)):]
|
||||
if err := requirePrivateRegularAtLinks(directory, sourceName, 1); err != nil {
|
||||
if requireSamePrivatePairAt(directory, sourceName, claimName) == nil {
|
||||
return false, nil
|
||||
}
|
||||
if isPrivateClaimAbsentOrOrphanAt(directory, sourceName, claimName) {
|
||||
return false, nil
|
||||
}
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
if err := unix.Linkat(directory, sourceName, directory, claimName, 0); err != nil {
|
||||
if errors.Is(err, unix.EEXIST) && privateRegularAtAllowedLinks(directory, claimName, 1, 2) == nil {
|
||||
return false, nil
|
||||
}
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
if err := requireSamePrivatePairAt(directory, sourceName, claimName); err != nil {
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
if err := unix.Fsync(directory); err != nil {
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func readCanonicalPrivateClaim(source, claim string, maximum int64) ([]byte, bool, error) {
|
||||
directory, sourceName, err := openCanonicalParentDirectory(source)
|
||||
if err != nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
defer unix.Close(directory)
|
||||
claimName := claim[len(claim)-len(claimBaseName(claim)):]
|
||||
if err := requireSamePrivatePairAt(directory, sourceName, claimName); err != nil {
|
||||
if isPrivateClaimAbsentOrOrphanAt(directory, sourceName, claimName) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
descriptor, err := unix.Openat(directory, sourceName, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
file := os.NewFile(uintptr(descriptor), "tht-safeio-oidc-claim")
|
||||
if file == nil {
|
||||
unix.Close(descriptor)
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
defer file.Close()
|
||||
var before unix.Stat_t
|
||||
if err := unix.Fstat(descriptor, &before); err != nil || !privateRegularStatWithLinks(before, 2) {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
contents, err := io.ReadAll(io.LimitReader(file, maximum+1))
|
||||
if err != nil || int64(len(contents)) > maximum {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
var after unix.Stat_t
|
||||
if err := unix.Fstat(descriptor, &after); err != nil || !sameUnixPrivateFile(before, after) || !privateRegularStatWithLinks(after, 2) {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
if err := requireSamePrivatePairAt(directory, sourceName, claimName); err != nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
return contents, true, nil
|
||||
}
|
||||
|
||||
func removeCanonicalPrivateClaim(source, claim string) (bool, error) {
|
||||
directory, sourceName, err := openCanonicalParentDirectory(source)
|
||||
if err != nil {
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
defer unix.Close(directory)
|
||||
claimName := claim[len(claim)-len(claimBaseName(claim)):]
|
||||
if err := requireSamePrivatePairAt(directory, sourceName, claimName); err != nil {
|
||||
if isPrivateClaimAbsentOrOrphanAt(directory, sourceName, claimName) {
|
||||
return false, nil
|
||||
}
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
if err := unix.Unlinkat(directory, sourceName, 0); err != nil {
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
if err := requirePrivateRegularAtLinks(directory, claimName, 1); err != nil {
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
if err := unix.Unlinkat(directory, claimName, 0); err != nil {
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
if err := unix.Fsync(directory); err != nil {
|
||||
return false, ErrUnsafeFile
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func claimBaseName(path string) string {
|
||||
for index := len(path) - 1; index >= 0; index-- {
|
||||
if path[index] == byte(os.PathSeparator) {
|
||||
return path[index+1:]
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func privateRegularAtAllowedLinks(directory int, name string, allowed ...uint64) error {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstatat(directory, name, &stat, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
for _, links := range allowed {
|
||||
if privateRegularStatWithLinks(stat, links) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
|
||||
func requirePrivateRegularAtLinks(directory int, name string, links uint64) error {
|
||||
return privateRegularAtAllowedLinks(directory, name, links)
|
||||
}
|
||||
|
||||
func requireSamePrivatePairAt(directory int, source, claim string) error {
|
||||
var left, right unix.Stat_t
|
||||
if err := unix.Fstatat(directory, source, &left, unix.AT_SYMLINK_NOFOLLOW); err != nil || !privateRegularStatWithLinks(left, 2) {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
if err := unix.Fstatat(directory, claim, &right, unix.AT_SYMLINK_NOFOLLOW); err != nil || !privateRegularStatWithLinks(right, 2) {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
if !sameUnixPrivateFile(left, right) {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isPrivateClaimAbsentOrOrphanAt(directory int, source, claim string) bool {
|
||||
var sourceStat, claimStat unix.Stat_t
|
||||
sourceErr := unix.Fstatat(directory, source, &sourceStat, unix.AT_SYMLINK_NOFOLLOW)
|
||||
claimErr := unix.Fstatat(directory, claim, &claimStat, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if errors.Is(sourceErr, unix.ENOENT) && errors.Is(claimErr, unix.ENOENT) {
|
||||
return true
|
||||
}
|
||||
return errors.Is(sourceErr, unix.ENOENT) && claimErr == nil && privateRegularStatWithLinks(claimStat, 1)
|
||||
}
|
||||
|
||||
func privateRegularStatWithLinks(stat unix.Stat_t, links uint64) bool {
|
||||
return stat.Mode&unix.S_IFMT == unix.S_IFREG && uint64(stat.Nlink) == links && stat.Mode&0o7777 == 0o600
|
||||
}
|
||||
|
||||
func sameUnixPrivateFile(left, right unix.Stat_t) bool {
|
||||
return left.Dev == right.Dev && left.Ino == right.Ino && left.Size == right.Size && left.Mtim == right.Mtim
|
||||
}
|
||||
Reference in New Issue
Block a user