security: reject WebDAV virtual-root mutation

This commit is contained in:
OpenClaw Agent
2026-09-21 22:38:36 +08:00
parent 18d5d29a09
commit cfe7295a96
+26 -4
View File
@@ -981,6 +981,9 @@ func (f *webdavStorageFS) Create(ctx context.Context, name string, body io.ReadC
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
if isVirtualStorageRoot(f.store, p) {
return nil, false, virtualRootMutationError()
}
_, statErr := f.store.Stat(p) _, statErr := f.store.Stat(p)
if statErr != nil && !os.IsNotExist(statErr) { if statErr != nil && !os.IsNotExist(statErr) {
return nil, false, statErr return nil, false, statErr
@@ -1002,13 +1005,26 @@ func (f *webdavStorageFS) Create(ctx context.Context, name string, body io.ReadC
return fi, true, err return fi, true, err
} }
func isVirtualStorageRoot(store storage.Storage, p string) bool {
m, ok := store.(storage.Multi)
if !ok {
return false
}
_, rel, found := m.FindRoot(p)
return found && rel == ""
}
func virtualRootMutationError() error {
return webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("cannot modify virtual root"))
}
func (f *webdavStorageFS) RemoveAll(ctx context.Context, name string, opts *webdav.RemoveAllOptions) error { func (f *webdavStorageFS) RemoveAll(ctx context.Context, name string, opts *webdav.RemoveAllOptions) error {
p, err := davSafePath(name) p, err := davSafePath(name)
if err != nil { if err != nil {
return err return err
} }
if p == "" { if p == "" || isVirtualStorageRoot(f.store, p) {
return fmt.Errorf("cannot remove storage root") return virtualRootMutationError()
} }
if _, err := f.store.Stat(p); err != nil { if _, err := f.store.Stat(p); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -1038,8 +1054,8 @@ func (f *webdavStorageFS) Mkdir(ctx context.Context, name string) error {
if err != nil { if err != nil {
return err return err
} }
if p == "" { if p == "" || isVirtualStorageRoot(f.store, p) {
return fmt.Errorf("cannot create storage root") return virtualRootMutationError()
} }
if _, err := f.store.Stat(p); err == nil { if _, err := f.store.Stat(p); err == nil {
return webdav.NewHTTPError(http.StatusMethodNotAllowed, fmt.Errorf("resource already exists")) return webdav.NewHTTPError(http.StatusMethodNotAllowed, fmt.Errorf("resource already exists"))
@@ -1061,6 +1077,9 @@ func (f *webdavStorageFS) Copy(ctx context.Context, name, dest string, opts *web
if sameOrDescendantPath(src, dst) { if sameOrDescendantPath(src, dst) {
return false, webdav.NewHTTPError(http.StatusConflict, fmt.Errorf("destination is inside source")) return false, webdav.NewHTTPError(http.StatusConflict, fmt.Errorf("destination is inside source"))
} }
if isVirtualStorageRoot(f.store, src) || isVirtualStorageRoot(f.store, dst) {
return false, virtualRootMutationError()
}
_, dstErr := f.store.Stat(dst) _, dstErr := f.store.Stat(dst)
dstExists := dstErr == nil dstExists := dstErr == nil
if dstErr != nil && !os.IsNotExist(dstErr) { if dstErr != nil && !os.IsNotExist(dstErr) {
@@ -1130,6 +1149,9 @@ func (f *webdavStorageFS) Move(ctx context.Context, name, dest string, opts *web
if sameOrDescendantPath(src, dst) { if sameOrDescendantPath(src, dst) {
return false, webdav.NewHTTPError(http.StatusConflict, fmt.Errorf("destination is inside source")) return false, webdav.NewHTTPError(http.StatusConflict, fmt.Errorf("destination is inside source"))
} }
if isVirtualStorageRoot(f.store, src) || isVirtualStorageRoot(f.store, dst) {
return false, virtualRootMutationError()
}
if _, err := f.store.Stat(dst); err == nil { if _, err := f.store.Stat(dst); err == nil {
// CHFS v4 MOVE does not overwrite an existing destination, even when // CHFS v4 MOVE does not overwrite an existing destination, even when
// Overwrite is omitted/T and the caller has delete permission. // Overwrite is omitted/T and the caller has delete permission.