~welt/murse

c3eb8485bf0d906975cf9323b92ef9f30fc4a385 — welt 3 months ago b2c8ae7
Catch when murse tries to write outside of the target directory for security purposes.
3 files changed, 93 insertions(+), 30 deletions(-)

M files.go
M upgrade.go
M verify.go
M files.go => files.go +22 -0
@@ 1,12 1,15 @@
package main

import (
	"errors"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

var ErrEscapesPath = errors.New("the path supplied would escape the install directory")

func getInstalledRevision(path string) (int, error) {
	rev := -1
	bytes, err := os.ReadFile(filepath.Join(filepath.FromSlash(path), ".revision"))


@@ 19,3 22,22 @@ func getInstalledRevision(path string) (int, error) {

	return strconv.Atoi(strings.TrimSpace(string(bytes)))
}

func resolvePath(installpath, changepath string) (string, error) {
	absinst, err := filepath.Abs(installpath)
	if err != nil {
		return "", err
	}

	absfinal, err := filepath.Abs(filepath.Join(absinst, filepath.FromSlash(changepath)))
	if err != nil {
		return "", err
	}

	if !strings.HasPrefix(absfinal, absinst) {
		return "", ErrEscapesPath
	}

	return absfinal, nil

}

M upgrade.go => upgrade.go +43 -15
@@ 5,7 5,7 @@ import (
	"io"
	"os"
	"path/filepath"
	"runtime/debug"
	"sort"
	"strconv"
	"strings"
)


@@ 38,7 38,6 @@ func upgradeMain(dir string, url string, threads int, http2 bool, owGameInfo boo
	for i := instRev + 1; i != latestRev+1; i++ {
		rev, err := client.GetRevision(i)
		if err != nil {
			debug.PrintStack()
			errPrintln(err)
			return 1
		}


@@ 50,27 49,50 @@ func upgradeMain(dir string, url string, threads int, http2 bool, owGameInfo boo
	writes, mkdirs, dels := splitChanges(changes)

	for _, v := range dels {
		fmt.Println("DEL", v.Path)
		path := filepath.FromSlash(v.Path)
		err := os.Remove(filepath.Join(dir, path))
		path, err := resolvePath(dir, v.Path)
		if err != nil {
			errPrintln(err)
			return 1
		}

		err = os.Remove(path)
		if err != nil && !os.IsNotExist(err) {
			errPrintln(err)
			return 1
		}

		fmt.Println("DEL", v.Path)
	}

	var mkdirpaths []string
	for _, v := range mkdirs {
		path := filepath.FromSlash(v.Path)
		err := os.RemoveAll(path)
		mkdirpaths = append(mkdirpaths, v.Path)
	}

	// Sort so that parents are done first,
	// then children.
	sort.Strings(mkdirpaths)

	for _, change := range mkdirpaths {
		path, err := resolvePath(dir, change)
		if err != nil {
			errPrintln(err)
			return 1
		}
		err = os.MkdirAll(filepath.Join(dir, path), 0777)

		err = os.RemoveAll(path)
		if err != nil {
			errPrintln(err)
			return 1
		}

		err = os.MkdirAll(path, 0777)
		if err != nil {
			errPrintln(err)
			return 1
		}

		fmt.Println("MKDIR", change)
	}

	pool := NewPool(threads)


@@ 80,6 102,7 @@ func upgradeMain(dir string, url string, threads int, http2 bool, owGameInfo boo
		case err := <-pool.Errch:
			if err != nil {
				pool.Stop()
				errPrintln(err)
				for {
					select {
					case err := <-pool.Errch:


@@ 102,11 125,17 @@ func upgradeMain(dir string, url string, threads int, http2 bool, owGameInfo boo

	for _, v := range writes {
		change := v
		path := filepath.FromSlash(change.Path)
		fpath := filepath.Join(dir, path)
		path, err := resolvePath(dir, v.Path)
		if err != nil {
			pool.Stop()
			dealErr()
			errPrintln(err)
			return 1
		}

		f := func() error {
			if !owGameInfo && isOF && change.Path == "gameinfo.txt" {
				exists, err := isExists(fpath)
				exists, err := isExists(path)
				if err != nil {
					return err
				}


@@ 125,11 154,11 @@ func upgradeMain(dir string, url string, threads int, http2 bool, owGameInfo boo

			defer object.Close()

			err = os.RemoveAll(fpath)
			err = os.RemoveAll(path)
			if err != nil {
				return err
			}
			file, err := os.Create(fpath)
			file, err := os.Create(path)
			if err != nil {
				return err
			}


@@ 142,8 171,7 @@ func upgradeMain(dir string, url string, threads int, http2 bool, owGameInfo boo

		pool.Jobch <- f

		ok := dealErr()
		if !ok {
		if ok := dealErr(); !ok {
			return 1
		}
	}

M verify.go => verify.go +28 -15
@@ 42,34 42,42 @@ func verifyMain(dir string, url string, repair bool, http2 bool, owGameInfo bool

	writes, mkdirs, deletes := splitChanges(changes)

	for _, v := range deletes {
		path := filepath.Join(dir, filepath.FromSlash(v.Path))
		_, err := os.Stat(path)
	for _, change := range deletes {
		path, err := resolvePath(dir, change.Path)
		if err != nil {
			errPrintln(err)
			return 1
		}
		_, err = os.Stat(path)
		if err != nil {
			if !os.IsNotExist(err) {
				errPrintln(err)
				return 1
			}
		} else {
			errPrintln("EXISTS", v.Path)
			errPrintln("EXISTS", change.Path)
			if repair {
				err = os.Remove(path)
				if err != nil {
					errPrintln(err)
					return 1
				}
				fmt.Println("FIXDIR", v.Path)
				fmt.Println("FIXDIR", change.Path)
			}
		}
	}

	for _, v := range mkdirs {
		path := filepath.Join(dir, filepath.FromSlash(v.Path))
	for _, change := range mkdirs {
		path, err := resolvePath(dir, change.Path)
		if err != nil {
			errPrintln(err)
			return 1
		}

		stat, err := os.Stat(path)
		if err != nil {
			if os.IsNotExist(err) {
				fmt.Println("MISSING", v.Path)
				fmt.Println("MISSING", change.Path)
				if repair {
					err := os.MkdirAll(path, 0777)
					if err != nil {


@@ 77,7 85,7 @@ func verifyMain(dir string, url string, repair bool, http2 bool, owGameInfo bool
						return 1
					}

					fmt.Println("FIXDIR", v.Path)
					fmt.Println("FIXDIR", change.Path)

				}
			} else {


@@ 86,7 94,7 @@ func verifyMain(dir string, url string, repair bool, http2 bool, owGameInfo bool
			}
		} else {
			if !stat.IsDir() {
				fmt.Println("NOTDIR", v.Path)
				fmt.Println("NOTDIR", change.Path)
				if repair {
					err := os.RemoveAll(path)
					if err != nil {


@@ 100,7 108,7 @@ func verifyMain(dir string, url string, repair bool, http2 bool, owGameInfo bool
						return 1
					}

					fmt.Println("FIXDIR", v.Path)
					fmt.Println("FIXDIR", change.Path)

				}
			}


@@ 110,12 118,17 @@ func verifyMain(dir string, url string, repair bool, http2 bool, owGameInfo bool
	isOF := (filepath.Dir(dir) == "open_fortress")

	for _, change := range writes {
		fpath := filepath.Join(dir, filepath.FromSlash(change.Path))
		path, err := resolvePath(dir, change.Path)
		if err != nil {
			errPrintln(err)
			return 1
		}

		var fix func() error
		if repair {
			fix = func() error {
				if !owGameInfo && isOF && change.Path == "gameinfo.txt" {
					exists, err := isExists(fpath)
					exists, err := isExists(path)
					if err != nil {
						return err
					}


@@ 133,7 146,7 @@ func verifyMain(dir string, url string, repair bool, http2 bool, owGameInfo bool

				defer object.Close()

				file, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777)
				file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777)
				if err != nil {
					return err
				}


@@ 148,7 161,7 @@ func verifyMain(dir string, url string, repair bool, http2 bool, owGameInfo bool
			}
		}

		file, err := os.Open(fpath)
		file, err := os.Open(path)
		if err != nil {
			if !os.IsNotExist(err) {
				errPrintln(err)