Potential memory corruption in fileio.c:write_file() since 3.0.20
with write cache size
Jean-Francois Panisset
panisset at A52.com
Fri Aug 3 15:06:46 GMT 2007
server: Samba 3.0.23c on CentOS5 x86_64
client: Autodesk Maya 8.5 (a 3D modeling and animation application)
on Windows XP SP2 32 bit
After upgrading from Fedora Core 4/Samba 3.0.23a, I started
experiencing mysterious hangs with Samba, which seemed to be
triggered when writing large scene files from Maya 8.5 in Maya Binary
format to a Samba share.
After some digging, I came to the following conclusion:
The share in question had:
write cache size = 32768
set in smb.conf. Turns out that when writing out largish files
(around 50MB), in some cases Maya likes to write a single byte 49152
bytes past the current end of file. This would trigger the following
optimization in write_file() in smbd/fileio.c around line 510:
} else if ( (pos >= wcp->file_size) &&
(n == 1) &&
(pos < wcp->offset + 2*wcp->alloc_size) &&
(wcp->file_size == wcp->offset + wcp->data_size)) {
SMB_BIG_UINT new_start = wcp->offset + wcp->data_size;
flush_write_cache(fsp, WRITE_FLUSH);
wcp->offset = new_start;
wcp->data_size = pos - new_start + 1;
memset(wcp->data, '\0', wcp->data_size);
memcpy(wcp->data + wcp->data_size-1, data, 1);
an optimization which appeared in 3.0.20, and claims to help with
Microsoft Office behavior. With "write cache size" set to 32768, this
means that wcp->alloc_size is 32768. If the application tries to
write 1 byte 49152 bytes past the end of the file, the condition will
trigger, and the call to memset() will write past the end of the
32768 byte long piece of memory allocated for the write cache
bufferby setup_write_cache(), causing heap corruption. valgrind
confirms that this is happening. In theory, I should have been seeing
this with 3.0.23a on FC4 as well, but I guess I just got lucky.
In my case, this would cause a call to free() to fail somewhere down
the line: the SIGABORT handler would try to pretty print a stack
trace or something, but that would in turn try to call malloc()/free
(), and hang on a futex() call. So the errant smbd would never
actually die, hold on to a lock on /var/cache/samba/locking.tbd, and
eventually all other smbds would grind to a halt when they would try
to lock locking.tdb
The fix appears to be simple: just make sure that the condition only
fires if you are trying to write no further than wcp->data_size from
the current end of file:
} else if ( (pos >= wcp->file_size) &&
(n == 1) &&
(pos < wcp->offset + wcp->alloc_size) &&
(wcp->file_size == wcp->offset + wcp->data_size)) {
Minimal testing shows this to work for my case, but I haven't looked
at the code in detail to make sure it always works. An alternative
could be to comment out the entire if() clause and do away with the
optimization.
This is my first attempt at looking at Samba internals, so I could
have missed something obvious...
Jean-Francois Panisset
A52/Rock Paper Scissors
More information about the samba-technical
mailing list