Index: wp-admin/includes/file.php
===================================================================
--- wp-admin/includes/file.php	(revision 14648)
+++ wp-admin/includes/file.php	(working copy)
@@ -306,10 +306,14 @@
 
 	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
 	if ( $test_type ) {
-		$wp_filetype = wp_check_filetype( $file['name'], $mimes );
+		$wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
 
 		extract( $wp_filetype );
 
+		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect
+		if ( $proper_filename )
+			$file['name'] = $proper_filename;
+
 		if ( ( !$type || !$ext ) && !current_user_can( 'unfiltered_upload' ) )
 			return call_user_func($upload_error_handler, $file, __( 'File type does not meet security guidelines. Try another.' ));
 
@@ -416,40 +420,14 @@
 
 	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
 	if ( $test_type ) {
-		$wp_filetype = wp_check_filetype( $file['name'], $mimes );
+		$wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
 
 		extract( $wp_filetype );
 
-		// If the file claims to be an image, validate it's extension
-		if ( function_exists('getimagesize') && !empty( $type ) && 'image/' == substr( $type, 0, 6 ) && is_uploaded_file( $file['tmp_name'] ) ) {
-			// Attempt to figure out what type of image it really is
-			$imgstats = @getimagesize( $file['tmp_name'] );
+		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect
+		if ( $proper_filename )
+			$file['name'] = $proper_filename;
 
-			// If getimagesize() knows what kind of image it really is and if the real MIME doesn't match the claimed MIME
-			if ( !empty($imgstats['mime']) && $imgstats['mime'] != $type ) {
-				// This is a simplified array of MIMEs that getimagesize() can detect and their extensions
-				$mime_to_ext = apply_filters( 'getimagesize_mimes_to_exts', array(
-					'image/jpeg' => 'jpg',
-					'image/png'  => 'png',
-					'image/gif'  => 'gif',
-					'image/bmp'  => 'bmp',
-					'image/tiff' => 'tif',
-				) );
-
-				// Replace whatever's after the last period in the filename with the correct extension
-				if ( !empty($mime_to_ext[$imgstats['mime']]) ) {
-					$filename_parts = explode( '.', $file['name'] );
-					array_pop( $filename_parts );
-					$filename_parts[] = $mime_to_ext[$imgstats['mime']];
-					$file['name'] = implode( '.', $filename_parts );
-
-					// Re-validate the extension / MIME
-					$wp_filetype = wp_check_filetype( $file['name'], $mimes );
-					extract( $wp_filetype );
-				}
-			}
-		}
-
 		if ( ( !$type || !$ext ) && !current_user_can( 'unfiltered_upload' ) )
 			return $upload_error_handler( $file, __( 'File type does not meet security guidelines. Try another.' ));
 
Index: wp-includes/functions.php
===================================================================
--- wp-includes/functions.php	(revision 14648)
+++ wp-includes/functions.php	(working copy)
@@ -2257,12 +2257,16 @@
  * @param null $deprecated Never used. Set to null.
  * @param mixed $bits File content
  * @param string $time Optional. Time formatted in 'yyyy/mm'.
+ * @param array $additional_args Optional. Additional arguments.
  * @return array
  */
-function wp_upload_bits( $name, $deprecated, $bits, $time = null ) {
+function wp_upload_bits( $name, $deprecated, $bits, $time = null, $additional_args = false ) {
 	if ( !empty( $deprecated ) )
 		_deprecated_argument( __FUNCTION__, '2.0' );
 
+	$default_additional_args = array( 'validate_extension' => true, 'mimes' => false );
+	$additional_args = wp_parse_args( $additional_args, $default_additional_args );
+
 	if ( empty( $name ) )
 		return array( 'error' => __( 'Empty filename' ) );
 
@@ -2301,6 +2305,19 @@
 	$perms = $perms & 0000666;
 	@ chmod( $new_file, $perms );
 
+	// Attempt to validate the extension as being correct
+	if ( $additional_args['validate_extension'] ) {
+		$wp_filetype = wp_check_filetype_and_ext( $new_file, $name, $additional_args['mimes'] );
+
+		// This will be set if the original filename was invalid
+		if ( $wp_filetype['proper_filename'] ) {
+			$filename = wp_unique_filename( $upload['path'], $wp_filetype['proper_filename'] );
+			$new_file_path = $upload['path'] . "/$filename";
+			rename( $new_file, $new_file_path );
+			$new_file = $new_file_path;
+		}
+	}
+
 	// Compute the URL
 	$url = $upload['url'] . "/$filename";
 
@@ -2363,6 +2380,74 @@
 }
 
 /**
+ * Attempt to determine the real file type of a file.
+ * If unable to, the file name extension will be used to determine type.
+ *
+ * If it's determined that the extension does not match the file's real type,
+ * then the "proper_filename" value will be set with a proper filename and extension.
+ *
+ * Currently this function only supports validating images known to getimagesize().
+ *
+ * @since 3.0
+ *
+ * @param string $file Full path to the image.
+ * @param string $filename The filename of the image (may differ from $file due to $file being in a tmp directory)
+ * @param array $mimes Optional. Key is the file extension with value as the mime type.
+ * @return array Values for the extension, MIME, and either a corrected filename or false if original $filename is valid
+ */
+function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
+
+	$proper_filename = false;
+
+	// Do basic extension validation and MIME mapping
+	$wp_filetype = wp_check_filetype( $filename, $mimes );
+	extract( $wp_filetype );
+
+	// We can't do any further validation without a file to work with
+	if ( !file_exists( $file ) )
+		return compact( 'ext', 'type', 'proper_filename' );
+
+	// We're able to validate images using GD
+	if ( $type && 'image/' == substr( $type, 0, 6 ) && function_exists('getimagesize') ) {
+
+		// Attempt to figure out what type of image it actually is
+		$imgstats = @getimagesize( $file );
+
+		// If getimagesize() knows what kind of image it really is and if the real MIME doesn't match the claimed MIME
+		if ( !empty($imgstats['mime']) && $imgstats['mime'] != $type ) {
+			// This is a simplified array of MIMEs that getimagesize() can detect and their extensions
+			// You shouldn't neeed to use this filter, but it's here just incase
+			$mime_to_ext = apply_filters( 'getimagesize_mimes_to_exts', array(
+				'image/jpeg' => 'jpg',
+				'image/png'  => 'png',
+				'image/gif'  => 'gif',
+				'image/bmp'  => 'bmp',
+				'image/tiff' => 'tif',
+			) );
+
+			// Replace whatever's after the last period in the filename with the correct extension
+			if ( !empty($mime_to_ext[$imgstats['mime']]) ) {
+				$filename_parts = explode( '.', $filename );
+				array_pop( $filename_parts );
+				$filename_parts[] = $mime_to_ext[$imgstats['mime']];
+				$new_filename = implode( '.', $filename_parts );
+
+				if ( $new_filename != $filename )
+					$proper_filename = $new_filename; // Mark that it changed
+
+				// Redefine the extension / MIME
+				$wp_filetype = wp_check_filetype( $new_filename, $mimes );
+				extract( $wp_filetype );
+			}
+		}
+	}
+
+	// Let plugins try and validate other types of files
+	// Should return an array in the style of array( 'ext' => $ext, 'type' => $type, 'proper_filename' => $proper_filename )
+	return apply_filters( 'wp_check_filetype_and_ext', compact( 'ext', 'type', 'proper_filename' ), $file, $filename, $mimes );
+}
+
+/**
  * Retrieve list of allowed mime types and file extensions.
  *
  * @since 2.8.6
