Skip to content

Commit ba94a48

Browse files
authored
SNOW-1155452 Fix race condition on perm checking for easy logging (#1382)
1 parent 14db80d commit ba94a48

6 files changed

+95
-40
lines changed

client_configuration.go

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"os"
88
"path"
99
"path/filepath"
10-
"runtime"
1110
"strings"
1211
)
1312

@@ -115,11 +114,9 @@ func parseClientConfiguration(filePath string) (*ClientConfig, error) {
115114
if filePath == "" {
116115
return nil, nil
117116
}
118-
fileContents, err := os.ReadFile(filePath)
119-
if err != nil {
120-
return nil, parsingClientConfigError(err)
121-
}
122-
err = validateCfgPerm(filePath)
117+
// Check if group (5th LSB) or others (2nd LSB) have a write permission to the file
118+
expectedPerm := os.FileMode(1<<4 | 1<<1)
119+
fileContents, err := getFileContents(filePath, expectedPerm)
123120
if err != nil {
124121
return nil, parsingClientConfigError(err)
125122
}
@@ -185,22 +182,6 @@ func validateLogLevel(clientConfig ClientConfig) error {
185182
return nil
186183
}
187184

188-
func validateCfgPerm(filePath string) error {
189-
if runtime.GOOS == "windows" {
190-
return nil
191-
}
192-
stat, err := os.Stat(filePath)
193-
if err != nil {
194-
return err
195-
}
196-
perm := stat.Mode()
197-
// Check if group (5th LSB) or others (2nd LSB) have a write permission to the file
198-
if perm&(1<<4) != 0 || perm&(1<<1) != 0 {
199-
return fmt.Errorf("configuration file: %s can be modified by group or others", filePath)
200-
}
201-
return nil
202-
}
203-
204185
func toLogLevel(logLevelString string) (string, error) {
205186
var logLevel = strings.ToUpper(logLevelString)
206187
switch logLevel {

client_configuration_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,21 @@ func TestUnknownValues(t *testing.T) {
298298
}
299299
}
300300

301+
func TestConfigFileOpenSymlinkFail(t *testing.T) {
302+
skipOnWindows(t, "file permission is different")
303+
dir := t.TempDir()
304+
configFilePath := createFile(t, defaultConfigName, "random content", dir)
305+
symlinkFile := path.Join(dir, "test_symlink")
306+
expectedErrMsg := "too many levels of symbolic links"
307+
308+
err := os.Symlink(configFilePath, symlinkFile)
309+
assertNilF(t, err, "failed to create symlink")
310+
311+
_, err = getFileContents(symlinkFile, os.FileMode(1<<4|1<<1))
312+
assertNotNilF(t, err, "should have blocked opening symlink")
313+
assertTrueF(t, strings.Contains(err.Error(), expectedErrMsg))
314+
}
315+
301316
func createFile(t *testing.T, fileName string, fileContents string, directory string) string {
302317
fullFileName := path.Join(directory, fileName)
303318
err := os.WriteFile(fullFileName, []byte(fileContents), 0644)

os_specific_posix.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package gosnowflake
44

55
import (
66
"fmt"
7+
"io"
78
"os"
89
"syscall"
910
)
@@ -23,3 +24,40 @@ func provideOwnerFromStat(info os.FileInfo, filepath string) (uint32, error) {
2324
}
2425
return nativeStat.Uid, nil
2526
}
27+
28+
func getFileContents(filePath string, expectedPerm os.FileMode) ([]byte, error) {
29+
// open the file with read only and no symlink flags
30+
file, err := os.OpenFile(filePath, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
31+
if err != nil {
32+
return nil, err
33+
}
34+
defer file.Close()
35+
36+
// validate file permissions and owner
37+
if err = validateFilePermissionBits(file, expectedPerm); err != nil {
38+
return nil, err
39+
}
40+
if err = ensureFileOwner(file); err != nil {
41+
return nil, err
42+
}
43+
44+
// read the file
45+
fileContents, err := io.ReadAll(file)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
return fileContents, nil
51+
}
52+
53+
func validateFilePermissionBits(f *os.File, expectedPerm os.FileMode) error {
54+
fileInfo, err := f.Stat()
55+
if err != nil {
56+
return err
57+
}
58+
filePerm := fileInfo.Mode()
59+
if filePerm&expectedPerm != 0 {
60+
return fmt.Errorf("incorrect permissions of %s", f.Name())
61+
}
62+
return nil
63+
}

os_specific_windows.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,11 @@ import (
1010
func provideFileOwner(file *os.File) (uint32, error) {
1111
return 0, errors.New("provideFileOwner is unsupported on windows")
1212
}
13+
14+
func getFileContents(filePath string, expectedPerm os.FileMode) ([]byte, error) {
15+
fileContents, err := os.ReadFile(filePath)
16+
if err != nil {
17+
return nil, err
18+
}
19+
return fileContents, nil
20+
}

permissions_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ package gosnowflake
44

55
import (
66
"fmt"
7-
"golang.org/x/sys/unix"
87
"os"
98
"path"
109
"testing"
10+
11+
"golang.org/x/sys/unix"
1112
)
1213

1314
func TestConfigPermissions(t *testing.T) {
@@ -19,9 +20,6 @@ func TestConfigPermissions(t *testing.T) {
1920
{filePerm: 0600, isValid: true},
2021
{filePerm: 0500, isValid: true},
2122
{filePerm: 0400, isValid: true},
22-
{filePerm: 0300, isValid: true},
23-
{filePerm: 0200, isValid: true},
24-
{filePerm: 0100, isValid: true},
2523
{filePerm: 0707, isValid: false},
2624
{filePerm: 0706, isValid: false},
2725
{filePerm: 0705, isValid: true},
@@ -47,7 +45,11 @@ func TestConfigPermissions(t *testing.T) {
4745
err := os.WriteFile(tempFile, nil, os.FileMode(tc.filePerm))
4846
assertNilE(t, err)
4947
defer os.Remove(tempFile)
50-
err = validateCfgPerm(tempFile)
48+
f, err := os.Open(tempFile)
49+
assertNilE(t, err)
50+
defer f.Close()
51+
expectedPerm := os.FileMode(1<<4 | 1<<1)
52+
err = validateFilePermissionBits(f, expectedPerm)
5153
if err != nil && tc.isValid {
5254
t.Error(err)
5355
}

secure_storage_manager.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9-
"github.com/99designs/keyring"
109
"io"
1110
"os"
1211
"os/user"
@@ -16,6 +15,8 @@ import (
1615
"strings"
1716
"sync"
1817
"time"
18+
19+
"github.com/99designs/keyring"
1920
)
2021

2122
type tokenType string
@@ -215,11 +216,19 @@ func (ssm *fileBasedSecureStorageManager) withCacheFile(action func(*os.File)) {
215216
}
216217
}(cacheDir)
217218

218-
if err := ssm.ensurePermissionsAndOwner(cacheFile, 0600); err != nil {
219+
if err := ensureFileOwner(cacheFile); err != nil {
220+
logger.Warnf("failed to ensure owner for temporary cache file. %v", err)
221+
return
222+
}
223+
if err := ensureFilePermissions(cacheFile, 0600); err != nil {
219224
logger.Warnf("failed to ensure permission for temporary cache file. %v", err)
220225
return
221226
}
222-
if err := ssm.ensurePermissionsAndOwner(cacheDir, 0700|os.ModeDir); err != nil {
227+
if err := ensureFileOwner(cacheDir); err != nil {
228+
logger.Warnf("failed to ensure owner for temporary cache dir. %v", err)
229+
return
230+
}
231+
if err := ensureFilePermissions(cacheDir, 0700|os.ModeDir); err != nil {
223232
logger.Warnf("failed to ensure permission for temporary cache dir. %v", err)
224233
return
225234
}
@@ -334,16 +343,7 @@ func (ssm *fileBasedSecureStorageManager) credFilePath() string {
334343
return filepath.Join(ssm.credDirPath, credCacheFileName)
335344
}
336345

337-
func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File, expectedMode os.FileMode) error {
338-
fileInfo, err := f.Stat()
339-
if err != nil {
340-
return err
341-
}
342-
343-
if fileInfo.Mode().Perm() != expectedMode&os.ModePerm {
344-
return fmt.Errorf("incorrect permissions(%v, expected %v) for credential file", fileInfo.Mode(), expectedMode)
345-
}
346-
346+
func ensureFileOwner(f *os.File) error {
347347
ownerUID, err := provideFileOwner(f)
348348
if err != nil && !errors.Is(err, os.ErrNotExist) {
349349
return err
@@ -361,6 +361,17 @@ func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File,
361361
return nil
362362
}
363363

364+
func ensureFilePermissions(f *os.File, expectedMode os.FileMode) error {
365+
fileInfo, err := f.Stat()
366+
if err != nil {
367+
return err
368+
}
369+
if fileInfo.Mode().Perm() != expectedMode&os.ModePerm {
370+
return fmt.Errorf("incorrect permissions(%v, expected %v) for credential file", fileInfo.Mode(), expectedMode)
371+
}
372+
return nil
373+
}
374+
364375
func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile(cacheFile *os.File) (map[string]any, error) {
365376

366377
jsonData, err := io.ReadAll(cacheFile)

0 commit comments

Comments
 (0)