Skip to content

Commit

Permalink
fix: Implementing a limitation for the open()
Browse files Browse the repository at this point in the history
Implementing a limitation for the open() that limits opening files
by the list of the files that were opened during the initialization
step (__VU == 0).

For example, a code like:

```js
if (__VU >0) {
   JSON.parse(open("./arr.json"));
}
```

Should return an error.

Closes #1771
  • Loading branch information
olegbespalov committed Jan 4, 2022
1 parent c93adb3 commit 5555051
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 13 deletions.
4 changes: 4 additions & 0 deletions js/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ func (b *Bundle) instantiate(logger logrus.FieldLogger, rt *goja.Runtime, init *
unbindInit()
*init.ctxPtr = nil

if vuID == 0 {
init.allowOnlyOpenedFiles()
}

rt.SetRandSource(common.NewRandSource())

return nil
Expand Down
44 changes: 37 additions & 7 deletions js/initcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"go.k6.io/k6/js/modules/k6/metrics"
"go.k6.io/k6/js/modules/k6/ws"
"go.k6.io/k6/lib"
"go.k6.io/k6/lib/fsext"
"go.k6.io/k6/loader"
)

Expand Down Expand Up @@ -299,13 +300,8 @@ func (i *InitContext) Open(ctx context.Context, filename string, args ...string)
if filename[0:1] != afero.FilePathSeparator {
filename = afero.FilePathSeparator + filename
}
// Workaround for https://github.com/spf13/afero/issues/201
if isDir, err := afero.IsDir(fs, filename); err != nil {
return nil, err
} else if isDir {
return nil, fmt.Errorf("open() can't be used with directories, path: %q", filename)
}
data, err := afero.ReadFile(fs, filename)

data, err := readFile(fs, filename)
if err != nil {
return nil, err
}
Expand All @@ -317,6 +313,40 @@ func (i *InitContext) Open(ctx context.Context, filename string, args ...string)
return i.runtime.ToValue(string(data)), nil
}

func readFile(fileSystem afero.Fs, filename string) ([]byte, error) {
// Workaround for https://github.com/spf13/afero/issues/201
if isDir, err := afero.IsDir(fileSystem, filename); err != nil {
return nil, err
} else if isDir {
return nil, fmt.Errorf("open() can't be used with directories, path: %q", filename)
}

data, err := afero.ReadFile(fileSystem, filename)
if err == nil {
return data, nil
}

// loading different files per VU is not supported, so all files should are going
// to be used inside the scenario should be opened during the init step (without any conditions)
if errors.Is(err, fsext.ErrFileNeverOpenedBefore) {
return nil, fmt.Errorf("open() can't be used under the conditions, path: %q", filename)
}

return nil, err
}

// allowOnlyOpenedFiles enables seen only files
func (i *InitContext) allowOnlyOpenedFiles() {
fs := i.filesystems["file"]

alreadyOpenedFS, ok := fs.(fsext.OnlyOpenedEnabler)
if !ok {
return
}

alreadyOpenedFS.AllowOnlyOpened()
}

func getInternalJSModules() map[string]interface{} {
return map[string]interface{}{
"k6": k6.New(),
Expand Down
26 changes: 26 additions & 0 deletions js/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import (
"go.k6.io/k6/js/modules/k6/ws"
"go.k6.io/k6/lib"
_ "go.k6.io/k6/lib/executor" // TODO: figure out something better
"go.k6.io/k6/lib/fsext"
"go.k6.io/k6/lib/metrics"
"go.k6.io/k6/lib/testutils"
"go.k6.io/k6/lib/testutils/httpmultibin"
Expand Down Expand Up @@ -1157,6 +1158,31 @@ func TestVUIntegrationOpenFunctionErrorWhenSneaky(t *testing.T) {
assert.Contains(t, err.Error(), "only available in the init stage")
}

func TestVUDoesNotOpenUnderConditions(t *testing.T) {
t.Parallel()

baseFS := afero.NewMemMapFs()
data := `
if (__VU > 0) {
data = open("/home/somebody/test.json");
}
exports.default = function(data) {
console.log("hey")
}
`
require.NoError(t, afero.WriteFile(baseFS, "/home/somebody/test.json", []byte(`42`), os.ModePerm))
require.NoError(t, afero.WriteFile(baseFS, "/script.js", []byte(data), os.ModePerm))

fs := fsext.NewCacheOnReadFs(baseFS, afero.NewMemMapFs(), 0)

r, err := getSimpleRunner(t, "/script.js", data, fs)
require.NoError(t, err)

_, err = r.NewVU(1, 1, make(chan stats.SampleContainer, 100))
assert.Error(t, err)
assert.Contains(t, err.Error(), "open() can't be used under the conditions")
}

func TestVUIntegrationCookiesReset(t *testing.T) {
t.Parallel()
tb := httpmultibin.NewHTTPMultiBin(t)
Expand Down
6 changes: 3 additions & 3 deletions lib/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ func (arc *Archive) Write(out io.Writer) error {
normalizeAndAnonymizeURL(metaArc.PwdURL)
metaArc.Filename = getURLtoString(metaArc.FilenameURL)
metaArc.Pwd = getURLtoString(metaArc.PwdURL)
var actualDataPath, err = url.PathUnescape(path.Join(getURLPathOnFs(metaArc.FilenameURL)))
actualDataPath, err := url.PathUnescape(path.Join(getURLPathOnFs(metaArc.FilenameURL)))
if err != nil {
return err
}
Expand Down Expand Up @@ -286,7 +286,7 @@ func (arc *Archive) Write(out io.Writer) error {
if !ok {
continue
}
if cachedfs, ok := filesystem.(fsext.CacheOnReadFs); ok {
if cachedfs, ok := filesystem.(fsext.CacheLayerGetter); ok {
filesystem = cachedfs.GetCachingFs()
}

Expand Down Expand Up @@ -344,7 +344,7 @@ func (arc *Archive) Write(out io.Writer) error {
}

for _, filePath := range paths {
var fullFilePath = path.Clean(path.Join(name, filePath))
fullFilePath := path.Clean(path.Join(name, filePath))
// we either have opaque
if fullFilePath == actualDataPath {
madeLinkToData = true
Expand Down
54 changes: 52 additions & 2 deletions lib/fsext/cacheonread.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,77 @@
package fsext

import (
"errors"
"sync"
"time"

"github.com/spf13/afero"
)

// ErrFileNeverOpenedBefore represent an error when file never opened before
var ErrFileNeverOpenedBefore = errors.New("file wasn't opened before")

// CacheOnReadFs is wrapper around afero.CacheOnReadFs with the ability to return the filesystem
// that is used as cache
type CacheOnReadFs struct {
afero.Fs
cache afero.Fs

lock *sync.Mutex
openedOnly bool
opened map[string]struct{}
}

// OnlyOpenedEnabler enables the mode of FS that allows to open
// already opened files (e.g. serve from cache only)
type OnlyOpenedEnabler interface {
AllowOnlyOpened()
}

// CacheLayerGetter provide a direct access to a cache layer
type CacheLayerGetter interface {
GetCachingFs() afero.Fs
}

// NewCacheOnReadFs returns a new CacheOnReadFs
func NewCacheOnReadFs(base, layer afero.Fs, cacheTime time.Duration) afero.Fs {
return CacheOnReadFs{
return &CacheOnReadFs{
Fs: afero.NewCacheOnReadFs(base, layer, cacheTime),
cache: layer,

lock: &sync.Mutex{},
openedOnly: false,
opened: map[string]struct{}{},
}
}

// GetCachingFs returns the afero.Fs being used for cache
func (c CacheOnReadFs) GetCachingFs() afero.Fs {
func (c *CacheOnReadFs) GetCachingFs() afero.Fs { // nolint:ireturn
return c.cache
}

// AllowOnlyOpened enables the opened only mode of the CacheOnReadFs
func (c *CacheOnReadFs) AllowOnlyOpened() {
c.lock.Lock()
defer c.lock.Unlock()

c.openedOnly = true
}

// Open opens file and track the history of opened files
// if CacheOnReadFs is in the opened only mode it should return
// an error if file wasn't open before
func (c *CacheOnReadFs) Open(name string) (afero.File, error) { // nolint:ireturn
c.lock.Lock()
defer c.lock.Unlock()

if !c.openedOnly {
c.opened[name] = struct{}{}
} else {
if _, ok := c.opened[name]; !ok {
return nil, ErrFileNeverOpenedBefore
}
}

return c.Fs.Open(name)
}
2 changes: 1 addition & 1 deletion loader/readsource.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func ReadSource(
return nil, err
}
// TODO: don't do it in this way ...
err = afero.WriteFile(filesystems["file"].(fsext.CacheOnReadFs).GetCachingFs(), "/-", data, 0644)
err = afero.WriteFile(filesystems["file"].(fsext.CacheLayerGetter).GetCachingFs(), "/-", data, 0o644)
if err != nil {
return nil, fmt.Errorf("caching data read from -: %w", err)
}
Expand Down

0 comments on commit 5555051

Please sign in to comment.