Skip to contentSkip to conversation

directory offset overflows in 2.6.28

RFC PATCH In review Christoph Hellwig 3 participants 5 messages Dec 29, 2008 – Dec 30, 2008 XFS filesystem

lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 directory offset overflows in 2.6.28

John Stanley reported EOVERFLOW errors in readdir from his self-build glibc. I traced this down to glibc enabling d_off overflow checks in one of the about five million different getdents implementations.

In 2.6.28 Dave Woodhouse moved our readdir double buffering required for NFS4 readdirplus into nfsd and at that point we lost the capping of the directory offsets to 32 bit signed values. Johns glibc used getdents64 to even implement readdir for normal 32 bit offset dirents, and failed with EOVERFLOW only if this happens on the first dirent in a getdents call. I managed to come up with a testcase that uses raw getdents and does the EOVERFLOW check manually. We always hit it with our last entry due to the special end of directory marker.

The patch below is a dumb version of just putting back the masking, to make sure we have the same behavior as in 2.6.27 and earlier. I think we should at least hide it in a macro that is well-commented, but I suspect we also need to make sure that we never ever get bigger offsets in directories in some way.

fs/xfs/xfs_dir2_block.c +4 -3
Index: xfs/fs/xfs/xfs_dir2_block.c
===================================================================
@@ -517,9 +517,9 @@ xfs_dir2_block_getdents(
 		/*
 		 * If it didn't fit, set the final offset to here & return.
 		 */
-		if (filldir(dirent, dep->name, dep->namelen, cook,
+		if (filldir(dirent, dep->name, dep->namelen, cook & 0x7fffffff,
 			    ino, DT_UNKNOWN)) {
-			*offset = cook;
+			*offset = cook & 0x7fffffff;
 			xfs_da_brelse(NULL, bp);
 			return 0;
 		}
@@ -529,7 +529,8 @@ xfs_dir2_block_getdents(
 	 * Reached the end of the block.
 	 * Set the offset to a non-existent block 1 and return.
 	 */
-	*offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0);
+	*offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0) &
+			0x7fffffff;
 	xfs_da_brelse(NULL, bp);
 	return 0;
 }
fs/xfs/xfs_dir2_leaf.c +3 -3
Index: xfs/fs/xfs/xfs_dir2_leaf.c
===================================================================
@@ -1092,7 +1092,7 @@ xfs_dir2_leaf_getdents(
 		 * Won't fit.  Return to caller.
 		 */
 		if (filldir(dirent, dep->name, dep->namelen,
-			    xfs_dir2_byte_to_dataptr(mp, curoff),
+			    xfs_dir2_byte_to_dataptr(mp, curoff) & 0x7fffffff,
 			    ino, DT_UNKNOWN))
 			break;
 
@@ -1108,9 +1108,9 @@ xfs_dir2_leaf_getdents(
 	 * All done.  Set output offset value to current offset.
 	 */
 	if (curoff > xfs_dir2_dataptr_to_byte(mp, XFS_DIR2_MAX_DATAPTR))
-		*offset = XFS_DIR2_MAX_DATAPTR;
+		*offset = XFS_DIR2_MAX_DATAPTR & 0x7fffffff;
 	else
-		*offset = xfs_dir2_byte_to_dataptr(mp, curoff);
+		*offset = xfs_dir2_byte_to_dataptr(mp, curoff) & 0x7fffffff;
 	kmem_free(map);
 	if (bp)
 		xfs_da_brelse(NULL, bp);
fs/xfs/xfs_dir2_sf.c +8 -7
Index: xfs/fs/xfs/xfs_dir2_sf.c
===================================================================
@@ -752,8 +752,8 @@ xfs_dir2_sf_getdents(
 #if XFS_BIG_INUMS
 		ino += mp->m_inoadd;
 #endif
-		if (filldir(dirent, ".", 1, dot_offset, ino, DT_DIR)) {
-			*offset = dot_offset;
+		if (filldir(dirent, ".", 1, dot_offset & 0x7fffffff, ino, DT_DIR)) {
+			*offset = dot_offset & 0x7fffffff;
 			return 0;
 		}
 	}
@@ -766,8 +766,8 @@ xfs_dir2_sf_getdents(
 #if XFS_BIG_INUMS
 		ino += mp->m_inoadd;
 #endif
-		if (filldir(dirent, "..", 2, dotdot_offset, ino, DT_DIR)) {
-			*offset = dotdot_offset;
+		if (filldir(dirent, "..", 2, dotdot_offset & 0x7fffffff, ino, DT_DIR)) {
+			*offset = dotdot_offset & 0x7fffffff;
 			return 0;
 		}
 	}
@@ -791,14 +791,15 @@ xfs_dir2_sf_getdents(
 #endif
 
 		if (filldir(dirent, sfep->name, sfep->namelen,
-					    off, ino, DT_UNKNOWN)) {
-			*offset = off;
+			    off & 0x7fffffff, ino, DT_UNKNOWN)) {
+			*offset = off & 0x7fffffff;
 			return 0;
 		}
 		sfep = xfs_dir2_sf_nextentry(sfp, sfep);
 	}
 
-	*offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0);
+	*offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0) &
+			0x7fffffff;
 	return 0;
 }
 

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs

#1 lore raw
Quoted 5 lines · Christoph Hellwig

The patch below is a dumb version of just putting back the masking, to make sure we have the same behavior as in 2.6.27 and earlier. I think we should at least hide it in a macro that is well-commented, but I suspect we also need to make sure that we never ever get bigger offsets in directories in some way.

I think we need that macro sooner rather than later ;)

Quoted 6 lines

Index: xfs/fs/xfs/xfs_dir2_block.c
===================================================================

fs/xfs/xfs_dir2_block.c +0 -0
@@ -517,9 +517,9 @@ xfs_dir2_block_getdents(
 		/*

fs/xfs/xfs_dir2_block.c xfs_dir2_block_getdents( · line 525

Quoted 10 lines
fs/xfs/xfs_dir2_block.c +2 -2
 		 * If it didn't fit, set the final offset to here & return.
 		 */
-		if (filldir(dirent, dep->name, dep->namelen, cook,
+		if (filldir(dirent, dep->name, dep->namelen, cook & 0x7fffffff,
 			    ino, DT_UNKNOWN)) {
-			*offset = cook;
+			*offset = cook & 0x7fffffff;
 			xfs_da_brelse(NULL, bp);
 			return 0;
 		}

In this case, you can do the masking at the time cook is assigned. I haven't checked, but I suspect the rest will be the same. That will make the patch less invasive and with a macro somewhat cleaner...

Cheers,

Dave.

Signature · 7 lines
Dave Chinner
[email protected]

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs
#2 lore raw
Quoted 35 lines · Dave Chinner
Quoted 6 lines · Christoph Hellwig

The patch below is a dumb version of just putting back the masking, to make sure we have the same behavior as in 2.6.27 and earlier. I think we should at least hide it in a macro that is well-commented, but I suspect we also need to make sure that we never ever get bigger offsets in directories in some way.

I think we need that macro sooner rather than later ;)

Quoted 7 lines

Index: xfs/fs/xfs/xfs_dir2_block.c
===================================================================

fs/xfs/xfs_dir2_block.c +0 -0
@@ -517,9 +517,9 @@ xfs_dir2_block_getdents(
 		/*

fs/xfs/xfs_dir2_block.c xfs_dir2_block_getdents( · line 525

Quoted 10 lines
fs/xfs/xfs_dir2_block.c +2 -2
 		 * If it didn't fit, set the final offset to here & return.
 		 */
-		if (filldir(dirent, dep->name, dep->namelen, cook,
+		if (filldir(dirent, dep->name, dep->namelen, cook & 0x7fffffff,
 			    ino, DT_UNKNOWN)) {
-			*offset = cook;
+			*offset = cook & 0x7fffffff;
 			xfs_da_brelse(NULL, bp);
 			return 0;
 		}

In this case, you can do the masking at the time cook is assigned. I haven't checked, but I suspect the rest will be the same. That will make the patch less invasive and with a macro somewhat cleaner...

Quoted 4 lines

Cheers,

Dave.

I'm still sorting out the readdir changes in the FreeBSD port so I'm not exactly sure how this would
affect things there but I'm thinking FreeBSD does not have 32 bit readdir offset limits, so this change
might be another undo patch I would have to maintain.
So I agree, lets make this less invasive.

I've managed to change things at bit by passing an opaque ptr containing the uuio struct into a freebsd "filldir" function so my xfs changes so far
are minimal.

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs

#3 lore raw
In reply to Dave Chinner
Quoted 7 lines · Dave Chinner
Quoted 5 lines · Christoph Hellwig

The patch below is a dumb version of just putting back the masking, to make sure we have the same behavior as in 2.6.27 and earlier. I think we should at least hide it in a macro that is well-commented, but I suspect we also need to make sure that we never ever get bigger offsets in directories in some way.

Quoted 6 lines

I think we need that macro sooner rather than later ;)

In this case, you can do the masking at the time cook is assigned. I haven't checked, but I suspect the rest will be the same. That will make the patch less invasive and with a macro somewhat cleaner...

That way we could replace two assignment by one one time each in xfs_dir2_leaf.c and xfs_dir2_block.c.

I started working on the macro, but it seems even more hacky.

When looking at the big picture we have two problems:

- the end of directory marker which always seems to be always too large for 32bit values for 32 bit indices
- directories that actually are too large to be represented using 32 bit signed offsets. I guess we just can't support those for apps using the 32bit readdir interface and the EOVERFLOW is valid for those. The current approach as in < 2.6.28 and with this patch breaks that second case in subtile ways.

So, I'd just push the first hacky opencoded patch into 2.6.29 and -stable now to revert to the old behaviour with all it's faults, and in the meantime I'll look into a proper way finding a better end of directory indicator. That should also help Russell's BSD concerns.

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs

#4 lore raw
Quoted 30 lines · Christoph Hellwig
Quoted 7 lines · Dave Chinner
Quoted 5 lines · Christoph Hellwig

The patch below is a dumb version of just putting back the masking, to make sure we have the same behavior as in 2.6.27 and earlier. I think we should at least hide it in a macro that is well-commented, but I suspect we also need to make sure that we never ever get bigger offsets in directories in some way.

Quoted 6 lines

I think we need that macro sooner rather than later ;)

In this case, you can do the masking at the time cook is assigned. I haven't checked, but I suspect the rest will be the same. That will make the patch less invasive and with a macro somewhat cleaner...

That way we could replace two assignment by one one time each in xfs_dir2_leaf.c and xfs_dir2_block.c.

I started working on the macro, but it seems even more hacky.

When looking at the big picture we have two problems:

- the end of directory marker which always seems to be always too large for 32bit values for 32 bit indices
- directories that actually are too large to be represented using 32 bit signed offsets. I guess we just can't support those for apps using the 32bit readdir interface and the EOVERFLOW is valid for those. The current approach as in < 2.6.28 and with this patch breaks that second case in subtile ways.

Quoted 4 lines

So, I'd just push the first hacky opencoded patch into 2.6.29 and -stable now to revert to the old behaviour with all it's faults, and in the meantime I'll look into a proper way finding a better end of directory indicator. That should also help Russell's BSD concerns.

Ok, sounds like a plan.

Cheers,

Dave.

Signature · 7 lines
Dave Chinner
[email protected]

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs