Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f0efc31
[WIP] Read security descriptor from share
elfrucool Jan 22, 2025
bc5ef93
WIP: Set security descriptor
elfrucool Jan 22, 2025
5f9461b
Use public sddl repo
elfrucool Jan 22, 2025
b35f02d
Move security info request call to File
elfrucool Jan 23, 2025
63edf77
[WIP] Get security info using compound request
elfrucool Jan 23, 2025
b66229a
Fix compound request
elfrucool Jan 23, 2025
28c7cf1
Do not use create contexts on get security info
elfrucool Jan 27, 2025
60d1ae5
Support compound requests
elfrucool Jan 31, 2025
361aaed
Enforce LARGE_MTU cap on security compound call
elfrucool Feb 3, 2025
f44ef86
Revert all compound request commits
elfrucool Feb 3, 2025
ea18a4d
Improve SecurityInfo API
elfrucool Feb 3, 2025
155a45e
Fix go and sddl version
elfrucool Feb 3, 2025
b37b749
Fix github workflow
elfrucool Feb 3, 2025
292c4c3
Upgrade github workflow tooling
elfrucool Feb 3, 2025
14aa015
Don't wrap errors on Share.createFile()
elfrucool Feb 3, 2025
7904b28
Use newer linter version in withub workflow
elfrucool Feb 3, 2025
429f373
Merge remote-tracking branch 'origin/main' into handle-security-descr…
elfrucool Feb 3, 2025
aaea781
Downgrade github workflow tools
elfrucool Feb 3, 2025
11b9c79
Revert "Downgrade github workflow tools"
elfrucool Feb 3, 2025
f6e4a77
Merge remote-tracking branch 'origin/main' into handle-security-descr…
elfrucool Feb 5, 2025
c4da04d
Upgrade golangci-lint version
elfrucool Feb 5, 2025
13a372b
Add a test for security descriptor
elfrucool Feb 5, 2025
f7419da
Fix test
elfrucool Feb 5, 2025
06c6d6b
Do not close file on File.SecurityInfoRaw()
elfrucool Feb 5, 2025
f12d0da
Fix test
elfrucool Feb 5, 2025
e13f337
Try again to test set security descriptor
elfrucool Feb 6, 2025
4a93f27
Revert "Try again to test set security descriptor"
elfrucool Feb 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ jobs:
test_linux:
strategy:
matrix:
go: [1.20.2]
go: [1.23.3]
runs-on: ubuntu-22.04
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: |
sudo apt update
sudo apt install -y samba
Expand All @@ -18,7 +18,7 @@ jobs:
(echo Smbpasswd12345; echo Smbpasswd12345) | sudo smbpasswd -a -s smbuser
sudo cp ./.github/smb.conf /etc/samba/smb.conf
sudo smbd
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
Expand All @@ -30,27 +30,27 @@ jobs:
- run: |
cp ./.github/client_conf.json ./
go test -v -race ./...
- uses: golangci/golangci-lint-action@v3
- uses: golangci/golangci-lint-action@v6
with:
version: v1.52.2
skip-cache: true
args: --timeout=8m
test_windows:
strategy:
matrix:
go: [1.20.2]
go: [1.23.3]
runs-on: windows-2022
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: |
mkdir C:\tmp
net user smbuser Smbpasswd12345 /add
net share tmp="C:\tmp" /GRANT:smbuser,full
net share tmp2="C:\tmp" /GRANT:smbuser,read
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
Expand All @@ -62,9 +62,9 @@ jobs:
- run: |
cp ./.github/client_conf.json ./
go test -v -race ./...
- uses: golangci/golangci-lint-action@v3
- uses: golangci/golangci-lint-action@v6
with:
version: v1.52.2
skip-cache: true
args: --timeout=8m


217 changes: 217 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,38 @@
"github.com/cloudsoda/go-smb2/internal/msrpc"
. "github.com/cloudsoda/go-smb2/internal/smb2"
"github.com/cloudsoda/go-smb2/internal/utf16le"
"github.com/cloudsoda/sddl"
)

// SecurityInformationRequestFlags the data that is expected to be returned in Security Information
type SecurityInformationRequestFlags uint32

const (
// OwnerSecurityInformation (OWNER_SECURITY_INFORMATION) requests the owner SID to be returned
OwnerSecurityInformation SecurityInformationRequestFlags = 0x00000001
// GroupSecurityInformation (GROUP_SECURITY_INFORMATION) requests the group SID to be returned
GroupSecurityInformation SecurityInformationRequestFlags = 0x00000002
// DACLSecurityInformation (DACL_SECURITY_INFORMATION) requests the DACL to be returned
DACLSecurityInformation SecurityInformationRequestFlags = 0x00000004
// SACLSecurityInformation (SACL_SECURITY_INFORMATION) requests the SACL to be returned
SACLSecurityInformation SecurityInformationRequestFlags = 0x00000008
)

type SecurityDescriptorEncoder struct {
*sddl.SecurityDescriptor
}

var _ Encoder = (*SecurityDescriptorEncoder)(nil)

Check failure on line 41 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: Encoder (typecheck)

func (se *SecurityDescriptorEncoder) Encode(dest []byte) {
data := se.Binary()
copy(dest, data)
}

func (se *SecurityDescriptorEncoder) Size() int {
return len(se.Binary())
}

// Dialer contains options for func (*Dialer) Dial.
type Dialer struct {
MaxCreditBalance uint16 // if it's zero, clientMaxCreditBalance is used. (See feature.go for more details)
Expand Down Expand Up @@ -311,7 +341,7 @@
return fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
}

func (fs *Share) newFile(r CreateResponseDecoder, name string) *File {

Check failure on line 344 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: CreateResponseDecoder (typecheck)
fd := r.FileId().Decode()

fileStat := &FileStat{
Expand Down Expand Up @@ -982,7 +1012,125 @@
return fi, nil
}

// SecurityInfo returns the security descriptor of a file
func (fs *Share) SecurityInfo(name string, info SecurityInformationRequestFlags) (*sddl.SecurityDescriptor, error) {
data, err := fs.SecurityInfoRaw(name, info)
if err != nil {
return nil, err
}

sd, err := sddl.FromBinary(data)
if err != nil {
return nil, fmt.Errorf("parsing binary representation of security descriptor: %w", err)
}

return sd, nil
}

// SecurityInfoRaw returns the raw security descriptor of a file as a byte array
func (fs *Share) SecurityInfoRaw(name string, info SecurityInformationRequestFlags) ([]byte, error) {
const op = "secinfo"
name = normPath(name)
if err := validatePath(op, name, false); err != nil {
return nil, err
}

if info == 0 {
return nil, &os.PathError{
Op: op,
Path: name,
Err: fmt.Errorf("%w: one or more SecurityInformation values must be specified", os.ErrInvalid),
}
}

// info should have at least one value
if info == 0 {
return nil, &os.PathError{
Op: op,
Path: name,
Err: fmt.Errorf("%w: one or more SecurityInformation values must be specified", os.ErrInvalid),
}
}

// we need to have at least READ_CONTROL in order to get the security descriptor
var access uint32 = READ_CONTROL // TODO: create a dedicated type for access mask
if info&SACLSecurityInformation != 0 {
access |= ACCESS_SYSTEM_SECURITY
}

creq := &CreateRequest{
SecurityFlags: 0,
RequestedOplockLevel: SMB2_OPLOCK_LEVEL_NONE,
ImpersonationLevel: Impersonation,
SmbCreateFlags: 0,
DesiredAccess: access,
FileAttributes: FILE_ATTRIBUTE_NORMAL,
ShareAccess: FILE_SHARE_READ,
CreateDisposition: FILE_OPEN,
CreateOptions: 0,
Mapping: fs.mapping,
}

f, err := fs.createFile(name, creq, true)
if err != nil {
return nil, &os.PathError{Op: op, Path: name, Err: fmt.Errorf("calling createFile: %w", err)}
}
defer f.Close() // in case we exit earlier

return f.SecurityInfoRaw(info)
}

func (fs *Share) SetSecurityInfo(name string, flags SecurityInformationRequestFlags, sd *sddl.SecurityDescriptor) error {
return fs.SetSecurityInfoRaw(name, flags, &SecurityDescriptorEncoder{sd})
}

func (fs *Share) SetSecurityInfoRaw(name string, flags SecurityInformationRequestFlags, sd Encoder) error {

Check failure on line 1087 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: Encoder (typecheck)
const op = "setsecinfo"
name = normPath(name)
if err := validatePath(op, name, false); err != nil {
return err
}

if flags == 0 {
return &os.PathError{
Op: op,
Path: name,
Err: fmt.Errorf("%w: one or more SecurityInformation values must be specified", os.ErrInvalid),
}
}

// we need to have at least WRITE_DAC to set the security descriptor
var access uint32 = WRITE_DAC
if flags&SACLSecurityInformation != 0 {
access |= ACCESS_SYSTEM_SECURITY
}
if flags&OwnerSecurityInformation != 0 {
access |= WRITE_OWNER
}

creq := &CreateRequest{
SecurityFlags: 0,
RequestedOplockLevel: SMB2_OPLOCK_LEVEL_NONE,
ImpersonationLevel: Impersonation,
SmbCreateFlags: 0,
DesiredAccess: access,
FileAttributes: FILE_ATTRIBUTE_NORMAL,
ShareAccess: FILE_SHARE_READ | FILE_SHARE_WRITE,
CreateDisposition: FILE_OPEN,
CreateOptions: 0,
Mapping: fs.mapping,
}

f, err := fs.createFile(name, creq, true)
if err != nil {
return &os.PathError{Op: op, Path: name, Err: fmt.Errorf("calling createFile: %w", err)}
}
defer f.Close()

return f.SetSecurityInfoRaw(flags, sd)
}

func (fs *Share) createFile(name string, req *CreateRequest, followSymlinks bool) (f *File, err error) {

Check failure on line 1133 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: CreateRequest (typecheck)
if followSymlinks {
return fs.createFileRec(name, req)
}
Expand Down Expand Up @@ -1014,7 +1162,7 @@
return f, nil
}

func (fs *Share) createFileRec(name string, req *CreateRequest) (f *File, err error) {

Check failure on line 1165 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: CreateRequest (typecheck)
for i := 0; i < clientMaxSymlinkDepth; i++ {
req.CreditCharge, _, err = fs.loanCredit(0)
defer func() {
Expand Down Expand Up @@ -1082,7 +1230,7 @@
return dir(ud) + target + u, nil
}

func (fs *Share) sendRecv(cmd uint16, req Packet) (res []byte, err error) {

Check failure on line 1233 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: Packet (typecheck)
rr, err := fs.send(req, fs.ctx)
if err != nil {
return nil, err
Expand All @@ -1102,7 +1250,7 @@

type File struct {
fs *Share
fd *FileId

Check failure on line 1253 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: FileId (typecheck)
name string
fileStat *FileStat
dirents []os.FileInfo
Expand Down Expand Up @@ -1971,14 +2119,14 @@
return f.Write([]byte(s))
}

func (f *File) encodeSize(e Encoder) int {

Check failure on line 2122 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: Encoder (typecheck)
if e == nil {
return 0
}
return e.Size()
}

func (f *File) ioctl(req *IoctlRequest) (output []byte, err error) {

Check failure on line 2129 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: IoctlRequest (typecheck)
payloadSize := f.encodeSize(req.Input) + int(req.OutputCount)
if payloadSize < int(req.MaxOutputResponse+req.MaxInputResponse) {
payloadSize = int(req.MaxOutputResponse + req.MaxInputResponse)
Expand Down Expand Up @@ -2087,7 +2235,7 @@
}
}

func (f *File) queryInfo(req *QueryInfoRequest) (infoBytes []byte, err error) {

Check failure on line 2238 in client.go

View workflow job for this annotation

GitHub Actions / test_linux (1.23.3)

undefined: QueryInfoRequest (typecheck)
payloadSize := f.encodeSize(req.Input)
if payloadSize < int(req.OutputBufferLength) {
payloadSize = int(req.OutputBufferLength)
Expand Down Expand Up @@ -2122,6 +2270,75 @@
return r.OutputBuffer(), nil
}

func (f *File) SecurityInfo(flags SecurityInformationRequestFlags) (*sddl.SecurityDescriptor, error) {
data, err := f.SecurityInfoRaw(flags)
if err != nil {
return nil, err
}

sd, err := sddl.FromBinary(data)
if err != nil {
return nil, fmt.Errorf("parsing binary representation of security descriptor: %w", err)
}

return sd, nil
}

func (f *File) SecurityInfoRaw(info SecurityInformationRequestFlags) ([]byte, error) {
op := "secinfo"
req := &QueryInfoRequest{
InfoType: SMB2_0_INFO_SECURITY, // TODO: rename info type constants to be more idiomatic with go
FileInfoClass: 0, // From the docs: "For security queries, this field MUST be set to 0"
OutputBufferLength: 64 * 1024, // Security descriptors have a max size of 64 KiB on NTFS: https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntquerysecurityobject#remarks
AdditionalInformation: uint32(info),
Flags: 0,
}
infoBytes, err := f.queryInfo(req)
if err != nil {
return nil, &os.PathError{Op: op, Path: f.name, Err: err}
}

if err := f.close(); err != nil {
return nil, &os.PathError{Op: op, Path: f.name, Err: err}
}

return infoBytes, nil
}

func (f *File) SetSecurityInfo(flags SecurityInformationRequestFlags, sd *sddl.SecurityDescriptor) error {
return f.SetSecurityInfoRaw(flags, &SecurityDescriptorEncoder{sd})
}

func (f *File) SetSecurityInfoRaw(flags SecurityInformationRequestFlags, sd Encoder) error {
op := "setsecinfo"

req := &SetInfoRequest{
InfoType: SMB2_0_INFO_SECURITY,
FileInfoClass: 0, // Not used for security info
AdditionalInformation: uint32(flags),
Input: sd,
FileId: f.fd,
}

var err error
req.CreditCharge, _, err = f.fs.loanCredit(req.Size())
defer func() {
if err != nil {
f.fs.chargeCredit(req.CreditCharge)
}
}()
if err != nil {
return fmt.Errorf("calling loanCredit: %w", err)
}

_, err = f.sendRecv(SMB2_SET_INFO, req)
if err != nil {
return &os.PathError{Op: op, Path: f.name, Err: err}
}

return nil
}

func (f *File) setInfo(req *SetInfoRequest) (err error) {
payloadSize := f.encodeSize(req.Input)

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module github.com/cloudsoda/go-smb2

go 1.20
go 1.23

require (
github.com/cloudsoda/sddl v0.0.0-20250203211958-38b2ba66781c
github.com/geoffgarside/ber v1.1.0
github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/stretchr/testify v1.8.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/cloudsoda/sddl v0.0.0-20250203211958-38b2ba66781c h1:7ehKjpHDKPsyXXX655yNSP4cFijNAYMtWgywSb8bO7s=
github.com/cloudsoda/sddl v0.0.0-20250203211958-38b2ba66781c/go.mod h1:xQSEAheX+AlO+KEijxcVd2jnU6oV1dLwZG2bE9FbYT4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
17 changes: 1 addition & 16 deletions internal/smb2/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ const (
ATTRIBUTE_SECUIRTY_INFORMATION
SCOPE_SECUIRTY_INFORMATION

BACKUP_SECUIRTY_INFORMATION = 0x10000
BACKUP_SECURITY_INFORMATION = 0x10000
)

// Flags
Expand Down Expand Up @@ -698,18 +698,3 @@ const (
// FileFsControlInformation
// FileFsObjectIdInformation
)

// AdditionalInformation
const (
// OWNER_SECURITY_INFORMATION = 1 << iota
// GROUP_SECUIRTY_INFORMATION
// DACL_SECUIRTY_INFORMATION
// SACL_SECUIRTY_INFORMATION
// LABEL_SECUIRTY_INFORMATION
// ATTRIBUTE_SECUIRTY_INFORMATION
// SCOPE_SECUIRTY_INFORMATION

// BACKUP_SECUIRTY_INFORMATION = 0x10000
)

//
Loading