filer: revalidate WORM under the commit lock

WORM was checked before the entry lock was acquired, so a concurrent
writer could enable it while an ingest, repack, or plain HTTP overwrite
waited, and the commit then replaced a protected entry. Repack now
checks under its lock, and ingest and saveMetaData recheck at commit
time.
This commit is contained in:
Chris Lu
2026-08-10 18:23:34 -07:00
parent 318e1c64d6
commit 045c834dcf
2 changed files with 26 additions and 6 deletions
+17 -5
View File
@@ -206,6 +206,16 @@ func (fs *FilerServer) formatIngest(ctx context.Context, w http.ResponseWriter,
// serialize with gRPC writers, renames, and repack
pathLock := fs.entryLockTable.AcquireLock("formatIngest", entry.FullPath, util.ExclusiveLock)
defer fs.entryLockTable.ReleaseLock(entry.FullPath, pathLock)
// recheck under the lock: WORM may have been enabled during the upload
if enforced, wormErr := fs.wormEnforcedForEntry(ctx, r.URL.Path); wormErr != nil {
cleanup()
writeJsonError(w, r, http.StatusInternalServerError, wormErr)
return
} else if enforced {
cleanup()
writeJsonError(w, r, http.StatusForbidden, errors.New("cannot replace WORM-enforced entry"))
return
}
if err := fs.filer.CreateEntry(context.WithoutCancel(ctx), entry, nil, false, false, nil, skipCheckParentDirEntry(r), so.MaxFileNameLength); err != nil {
cleanup()
writeJsonError(w, r, http.StatusInternalServerError, err)
@@ -230,6 +240,13 @@ func (fs *FilerServer) formatRepack(ctx context.Context, w http.ResponseWriter,
return
}
fullPath := util.FullPath(r.URL.Path)
// Serializes gRPC writers, renames, and this filer's HTTP overwrites;
// cross-filer serialization needs owner routing.
pathLock := fs.entryLockTable.AcquireLock("formatRepack", fullPath, util.ExclusiveLock)
defer fs.entryLockTable.ReleaseLock(fullPath, pathLock)
// checked under the lock so a concurrent WORM enable cannot land between
// validation and the entry swap
if enforced, err := fs.wormEnforcedForEntry(ctx, r.URL.Path); err != nil {
writeJsonError(w, r, http.StatusInternalServerError, err)
return
@@ -238,11 +255,6 @@ func (fs *FilerServer) formatRepack(ctx context.Context, w http.ResponseWriter,
return
}
// The lock covers gRPC writers and renames; plain HTTP overwrites do not
// take it, so repack targets should be quiescent.
pathLock := fs.entryLockTable.AcquireLock("formatRepack", fullPath, util.ExclusiveLock)
defer fs.entryLockTable.ReleaseLock(fullPath, pathLock)
entry, err := fs.filer.FindEntry(ctx, fullPath)
if err != nil {
if errors.Is(err, filer_pb.ErrNotFound) {
@@ -247,11 +247,19 @@ func (fs *FilerServer) saveMetaData(ctx context.Context, r *http.Request, fileNa
path := fs.fixFilePath(ctx, r, fileName)
// Commit under the entry lock so plain HTTP overwrites serialize with
// gRPC writers, renames, and format repack.
// gRPC writers, renames, and format repack on this filer; cross-filer
// serialization needs owner routing.
fullPath := util.FullPath(path)
pathLock := fs.entryLockTable.AcquireLock("saveMetaData", fullPath, util.ExclusiveLock)
defer fs.entryLockTable.ReleaseLock(fullPath, pathLock)
// recheck under the lock: WORM may have been enabled during the upload
if enforced, wormErr := fs.wormEnforcedForEntry(ctx, path); wormErr != nil {
return nil, wormErr
} else if enforced {
return nil, errors.New(constants.ErrMsgOperationNotPermitted)
}
var entry *filer.Entry
var newChunks []*filer_pb.FileChunk
var mergedChunks []*filer_pb.FileChunk