Index: src/wp-admin/css/customize-controls.css
===================================================================
--- src/wp-admin/css/customize-controls.css	(revision 36507)
+++ src/wp-admin/css/customize-controls.css	(working copy)
@@ -724,6 +724,7 @@
 .customize-control-background .current,
 .customize-control-cropped_image .current,
 .customize-control-site_icon .current,
+.customize-control-site_logo .current,
 .customize-control-header .current {
 	margin-bottom: 8px;
 }
@@ -765,6 +766,9 @@
 .customize-control-site_icon .remove-button,
 .customize-control-site_icon .default-button,
 .customize-control-site_icon .upload-button,
+.customize-control-site_logo .remove-button,
+.customize-control-site_logo .default-button,
+.customize-control-site_logo .upload-button,
 .customize-control-header button.new,
 .customize-control-header button.remove {
 	white-space: normal;
@@ -778,6 +782,7 @@
 .customize-control-background .current .container,
 .customize-control-cropped_image .current .container,
 .customize-control-site_icon .current .container,
+.customize-control-site_logo .current .container,
 .customize-control-header .current .container {
 	overflow: hidden;
 	-webkit-border-radius: 2px;
@@ -791,6 +796,7 @@
 .customize-control-background .current .container,
 .customize-control-cropped_image .current .container,
 .customize-control-site_icon .current .container,
+.customize-control-site_logo .current .container,
 .customize-control-image .current .container {
 	min-height: 40px;
 }
@@ -801,6 +807,7 @@
 .customize-control-background .placeholder,
 .customize-control-cropped_image .placeholder,
 .customize-control-site_icon .placeholder,
+.customize-control-site_logo .placeholder,
 .customize-control-header .placeholder {
 	width: 100%;
 	position: relative;
@@ -814,6 +821,7 @@
 .customize-control-background .inner,
 .customize-control-cropped_image .inner,
 .customize-control-site_icon .inner,
+.customize-control-site_logo .inner,
 .customize-control-header .inner {
 	display: none;
 	position: absolute;
@@ -829,6 +837,7 @@
 .customize-control-background .inner,
 .customize-control-cropped_image .inner,
 .customize-control-site_icon .inner,
+.customize-control-site_logo .inner,
 .customize-control-image .inner {
 	display: block;
 	min-height: 40px;
@@ -840,6 +849,7 @@
 .customize-control-background .inner,
 .customize-control-cropped_image .inner,
 .customize-control-site_icon .inner,
+.customize-control-site_logo.inner,
 .customize-control-header .inner,
 .customize-control-header .inner .dashicons {
 	line-height: 20px;
@@ -945,6 +955,7 @@
 .customize-control-background .actions,
 .customize-control-cropped_image .actions,
 .customize-control-site_icon .actions,
+.customize-control-site_logo .actions,
 .customize-control-header .actions {
 	margin-bottom: 32px;
 }
@@ -965,6 +976,7 @@
 .customize-control-background img,
 .customize-control-cropped_image img,
 .customize-control-site_icon img,
+.customize-control-site_logo img,
 .customize-control-header img {
 	width: 100%;
 	-webkit-border-radius: 2px;
@@ -983,6 +995,8 @@
 .customize-control-cropped_image .default-button,
 .customize-control-site_icon .remove-button,
 .customize-control-site_icon .default-button,
+.customize-control-site_logo .remove-button,
+.customize-control-site_logo .default-button,
 .customize-control-header .remove {
 	float: left;
 	margin-right: 3px;
@@ -994,6 +1008,7 @@
 .customize-control-background .upload-button,
 .customize-control-cropped_image .upload-button,
 .customize-control-site_icon .upload-button,
+.customize-control-site_logo .upload-button,
 .customize-control-header .new {
 	float: right;
 }
Index: src/wp-admin/includes/admin.php
===================================================================
--- src/wp-admin/includes/admin.php	(revision 36507)
+++ src/wp-admin/includes/admin.php	(working copy)
@@ -72,6 +72,9 @@
 /** WordPress Site Icon API */
 require_once(ABSPATH . 'wp-admin/includes/class-wp-site-icon.php');

+/** WordPress Site Logo API */
+require_once(ABSPATH . 'wp-admin/includes/class-wp-site-logo.php');
+
 /** WordPress Update Administration API */
 require_once(ABSPATH . 'wp-admin/includes/update.php');

Index: src/wp-admin/includes/class-wp-site-logo.php
===================================================================
--- src/wp-admin/includes/class-wp-site-logo.php	(revision 0)
+++ src/wp-admin/includes/class-wp-site-logo.php	(working copy)
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Administration API: WP_Site_Logo class
+ *
+ * @package WordPress
+ * @subpackage Administration
+ * @since 4.5.0
+ */
+
+/**
+ * Core class used to implement site logo functionality.
+ *
+ * @since 4.5.0
+ */
+class WP_Site_Logo {
+
+	/**
+	 * Get current logo settings stored in options.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 */
+	public function __construct() {
+		add_action( 'wp_head', array( $this, 'head_text_styles' ) );
+		add_action( 'delete_attachment', array( $this, 'delete_attachment_data' ) );
+		add_filter( 'image_size_names_choose', array( $this, 'media_manager_image_sizes' ) );
+	}
+
+	/**
+	 * Enqueue scripts for the Customizer live preview.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 */
+	public function preview_enqueue() {
+
+		// Don't bother passing in header text classes if the theme supports custom headers.
+		if ( ! current_theme_supports( 'custom-header' ) ) {
+			wp_enqueue_script( 'site-logo-header-text', plugins_url( '../js/site-logo-header-text.js', __FILE__ ), array( 'media-views' ), '', true );
+			wp_localize_script( 'site-logo-header-text', 'site_logo_header_classes', $this->header_text_classes() );
+		}
+	}
+
+	/**
+	 * Get header text classes. If not defined in add_theme_support(), defaults from Underscores will be used.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 *
+	 * @return string String of classes to hide
+	 */
+	public function header_text_classes() {
+		$args = get_theme_support( 'site-logo' );
+
+		if ( isset( $args[0]['header-text'] ) ) {
+			// Use any classes defined in add_theme_support().
+			$classes = $args[0]['header-text'];
+		} else {
+			// Otherwise, use these defaults, which will work with any Underscores-based theme.
+			$classes = array(
+				'site-title',
+				'site-description',
+			);
+		}
+
+		// If we've got an array, reduce them to a string for output
+		if ( is_array( $classes ) ) {
+			$classes = array_map( 'sanitize_html_class', $classes );
+			$classes = (string) '.' . implode( ', .', $classes );
+		} else {
+			$classes = (string) '.' . $classes;
+		}
+
+		return $classes;
+	}
+
+	/**
+	 * Hide header text on front-end if necessary.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 */
+	public function head_text_styles() {
+		// Bail if our theme supports custom headers.
+		if ( current_theme_supports( 'custom-header' ) || get_theme_mod( 'site_logo_header_text', true ) ) {
+			return;
+		}
+
+		// Is Display Header Text unchecked? If so, we need to hide our header text.
+		?>
+		<!-- Site Logo: hide header text -->
+		<style type="text/css">
+			<?php echo sanitize_html_class( $this->header_text_classes() ); ?>  {
+				position: absolute;
+				clip: rect(1px, 1px, 1px, 1px);
+			}
+		</style>
+	<?php
+	}
+
+	/**
+	 * Make custom image sizes available to the media manager.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 *
+	 * @param array $sizes
+	 * @return array All default and registered custom image sizes.
+	 */
+	public function media_manager_image_sizes( $sizes ) {
+		// Get an array of all registered image sizes.
+		$intermediate = get_intermediate_image_sizes();
+
+		// Have we got anything fun to work with?
+		if ( is_array( $intermediate ) && ! empty( $intermediate ) ) {
+			foreach ( $intermediate as $key => $size ) {
+				// If the size isn't already in the $sizes array, add it.
+				if ( ! array_key_exists( $size, $sizes ) ) {
+					$sizes[ $size ] = $size;
+				}
+			}
+		}
+
+		return $sizes;
+	}
+
+	/**
+	 * Reset the site logo if the current logo is deleted in the media manager.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 *
+	 * @param int $post_id
+	 */
+	public function delete_attachment_data( $post_id ) {
+		$site_logo_id = get_option( 'site_logo' );
+
+		if ( $site_logo_id && $site_logo_id == $post_id ) {
+			delete_option( 'site_logo' );
+		}
+	}
+
+	/**
+	 * Sanitize our header text Customizer setting.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 *
+	 * @param int|string $input
+	 * @return int|string 1 if checked, empty string if not checked.
+	 */
+	public function sanitize_checkbox( $input ) {
+		return ( 1 == $input ) ? 1 : '';
+	}
+}
+
+/**
+ * @global WP_Site_Logo $wp_site_logo
+ */
+$GLOBALS['wp_site_logo'] = new WP_Site_Logo;
Index: src/wp-admin/includes/template.php
===================================================================
--- src/wp-admin/includes/template.php	(revision 36507)
+++ src/wp-admin/includes/template.php	(working copy)
@@ -1748,13 +1748,17 @@
 		$media_states[] = __( 'Site Icon' );
 	}

+	if ( $post->ID == get_option( 'site_logo' ) ) {
+		$media_states[] = __( 'Logo' );
+	}
+
 	/**
 	 * Filter the default media display states for items in the Media list table.
 	 *
 	 * @since 3.2.0
 	 *
 	 * @param array $media_states An array of media states. Default 'Header Image',
-	 *                            'Background Image', 'Site Icon'.
+	 *                            'Background Image', 'Site Icon', 'Logo'.
 	 */
 	$media_states = apply_filters( 'display_media_states', $media_states );

Index: src/wp-admin/js/customize-controls.js
===================================================================
--- src/wp-admin/js/customize-controls.js	(revision 36507)
+++ src/wp-admin/js/customize-controls.js	(working copy)
@@ -2296,6 +2296,43 @@
 	});

 	/**
+	 * A control for selecting Site Logos.
+	 *
+	 * @class
+	 * @augments wp.customize.MediaControl
+	 * @augments wp.customize.Control
+	 * @augments wp.customize.Class
+	 */
+	api.SiteLogoControl = api.MediaControl.extend({
+
+		/**
+		 * When the control's DOM structure is ready,
+		 * set up internal event bindings.
+		 */
+		ready: function() {
+			var control = this;
+
+			// Shortcut so that we don't have to use _.bind every time we add a callback.
+			_.bindAll( control, 'restoreDefault', 'removeFile', 'openFrame', 'select' );
+
+			// Bind events, with delegation to facilitate re-rendering.
+			control.container.on( 'click keydown', '.upload-button', control.openFrame );
+			control.container.on( 'click keydown', '.thumbnail-image img', control.openFrame );
+			control.container.on( 'click keydown', '.default-button', control.restoreDefault );
+			control.container.on( 'click keydown', '.remove-button', control.removeFile );
+
+			control.setting.bind( function( attachmentId ) {
+				wp.media.attachment( attachmentId ).fetch().done( function() {
+					wp.customize.previewer.send( 'site-logo-attachment-data', this.attributes );
+				} );
+
+				// Re-render whenever the control's setting changes.
+				control.renderContent();
+			} );
+		}
+	});
+
+	/**
 	 * @class
 	 * @augments wp.customize.Control
 	 * @augments wp.customize.Class
@@ -3201,6 +3238,7 @@
 		image:         api.ImageControl,
 		cropped_image: api.CroppedImageControl,
 		site_icon:     api.SiteIconControl,
+		site_logo:     api.SiteLogoControl,
 		header:        api.HeaderControl,
 		background:    api.BackgroundControl,
 		theme:         api.ThemeControl
Index: src/wp-includes/class-wp-customize-manager.php
===================================================================
--- src/wp-includes/class-wp-customize-manager.php	(revision 36507)
+++ src/wp-includes/class-wp-customize-manager.php	(working copy)
@@ -208,6 +208,7 @@
 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php' );
 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php' );
 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php' );
+		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-logo-control.php' );
 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php' );
 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-theme-control.php' );
 		require_once( ABSPATH . WPINC . '/customize/class-wp-widget-area-customize-control.php' );
@@ -1804,6 +1805,7 @@
 		$this->register_control_type( 'WP_Customize_Background_Image_Control' );
 		$this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
 		$this->register_control_type( 'WP_Customize_Site_Icon_Control' );
+		$this->register_control_type( 'WP_Customize_Site_Logo_Control' );
 		$this->register_control_type( 'WP_Customize_Theme_Control' );

 		/* Themes */
@@ -1879,6 +1881,23 @@
 			'section'    => 'title_tagline',
 		) );

+		// Add a setting to hide header text if the theme isn't supporting the feature itself.
+		// @todo
+		if ( ! current_theme_supports( 'custom-header' ) ) {
+			$this->add_setting( 'header_text', array(
+				'default'           => 1,
+				'sanitize_callback' => 'absint',
+				'transport'         => 'postMessage',
+			) );
+
+			$this->add_control( 'header_text', array(
+				'label'    => __( 'Display Site Title and Tagline' ),
+				'section'  => 'title_tagline',
+				'settings' => 'header_text',
+				'type'     => 'checkbox',
+			) );
+		}
+
 		$this->add_setting( 'site_icon', array(
 			'type'       => 'option',
 			'capability' => 'manage_options',
@@ -1898,6 +1917,19 @@
 			'width'       => 512,
 		) ) );

+		$this->add_setting( 'site_logo', array(
+			'theme_supports' => array( 'site-logo' ),
+			'type'           => 'option',
+			'capability'     => 'manage_options',
+			'transport'      => 'postMessage',
+		) );
+
+		$this->add_control( new WP_Customize_Site_Logo_Control( $this, 'site_logo', array(
+			'label'    => __( 'Logo' ),
+			'section'  => 'title_tagline',
+			'priority' => 50,
+		) ) );
+
 		/* Colors */

 		$this->add_section( 'colors', array(
Index: src/wp-includes/customize/class-wp-customize-site-logo-control.php
===================================================================
--- src/wp-includes/customize/class-wp-customize-site-logo-control.php	(revision 0)
+++ src/wp-includes/customize/class-wp-customize-site-logo-control.php	(working copy)
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Customize API: WP_Customize_Site_Logo_Control class
+ *
+ * @package WordPress
+ * @subpackage Customize
+ * @since 4.5.0
+ */
+
+/**
+ * Customize Site Logo control class.
+ *
+ * Used only for custom functionality in JavaScript.
+ *
+ * @since 4.5.0
+ *
+ * @see WP_Customize_Image_Control
+ */
+class WP_Customize_Site_Logo_Control extends WP_Customize_Image_Control {
+
+	/**
+	 * Control type.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 * @var string
+	 */
+	public $type = 'site_logo';
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 *
+	 * @param WP_Customize_Manager $manager Customizer bootstrap instance.
+	 * @param string               $id      Control ID.
+	 * @param array                $args    Optional. Arguments to override class property defaults.
+	 */
+	public function __construct( $manager, $id, $args = array() ) {
+		parent::__construct( $manager, $id, $args );
+
+		$this->button_labels = array(
+			'select'       => __( 'Select logo' ),
+			'change'       => __( 'Change logo' ),
+			'remove'       => __( 'Remove' ),
+			'default'      => __( 'Default' ),
+			'placeholder'  => __( 'No logo selected' ),
+			'frame_title'  => __( 'Select logo' ),
+			'frame_button' => __( 'Choose logo' ),
+		);
+	}
+}
Index: src/wp-includes/general-template.php
===================================================================
--- src/wp-includes/general-template.php	(revision 36507)
+++ src/wp-includes/general-template.php	(working copy)
@@ -836,6 +836,77 @@
 }

 /**
+ * Whether the site has a Site Logo.
+ *
+ * @since 4.5.0
+ *
+ * @param int $blog_id Optional. ID of the blog in question. Default current blog.
+ * @return bool Whether the site has a site logo or not.
+ */
+function has_site_logo( $blog_id = 0 ) {
+	if ( is_multisite() && (int) $blog_id !== get_current_blog_id() ) {
+		switch_to_blog( $blog_id );
+	}
+
+	$site_logo_id = get_option( 'site_logo' );
+
+	if ( is_multisite() && ms_is_switched() ) {
+		restore_current_blog();
+	}
+
+	return (bool) $site_logo_id;
+}
+
+function get_the_site_logo( $blog_id = 0 ) {
+	$html = '';
+
+	if ( is_multisite() && (int) $blog_id !== get_current_blog_id() ) {
+		switch_to_blog( $blog_id );
+	}
+
+	$site_logo_id = get_option( 'site_logo' );
+
+	if ( is_multisite() && ms_is_switched() ) {
+		restore_current_blog();
+	}
+	$size = get_theme_support( 'site-logo' );
+	$size = $size[0]['size'];
+
+	// We have a logo. Logo is go.
+	if ( $site_logo_id ) {
+		$html = sprintf( '<a href="%1$s" class="site-logo-link" rel="home" itemprop="url">%2$s</a>',
+			esc_url( home_url( '/' ) ),
+			wp_get_attachment_image( $site_logo_id, $size, false, array(
+				'class'     => "site-logo attachment-$size",
+				'data-size' => $size,
+				'itemprop'  => 'logo',
+			) )
+		);
+	}
+
+	// If no logo is set but we're in the Customizer, leave a placeholder (needed for the live preview).
+	elseif ( is_customize_preview() ) {
+		$html = sprintf( '<a href="%1$s" class="site-logo-link" style="display:none;"><img class="site-logo" data-size="%2$s" /></a>',
+			esc_url( home_url( '/' ) ),
+			esc_attr( $size )
+		);
+	}
+
+	/**
+	 * Filter the Site Logo output.
+	 *
+	 * @since 4.5.0
+	 *
+	 * @param string $html Site Logo HTML output.
+	 * @param string $size Size specified in add_theme_support declaration, or 'thumbnail' default.
+	 */
+	return apply_filters( 'get_the_site_logo', $html, $size );
+}
+
+function the_site_logo( $blog_id = 0 ) {
+	echo get_the_site_logo( $blog_id );
+}
+/**
  * Returns document title for the current page.
  *
  * @since 4.4.0
Index: src/wp-includes/js/customize-preview.js
===================================================================
--- src/wp-includes/js/customize-preview.js	(revision 36507)
+++ src/wp-includes/js/customize-preview.js	(working copy)
@@ -223,6 +223,51 @@
 			});
 		});

+		/**
+		 * Site Logo
+		 *
+		 * The site logo setting only contains the attachment ID. To avoid having to send an AJAX request to get more
+		 * data, we send a separate message with the attachment data we get from the Customizer's media modal.
+		 * Therefore first callback handles only the event of a new logo being selected.
+		 *
+		 * We don't need any information about a removed logo, so the second callback only handles that.
+		 *
+		 * @since 4.5.0
+		 */
+		api.preview.bind( 'site-logo-attachment-data', function( attachment ) {
+			var $logo  = $( '.site-logo' ),
+				size   = $logo.data( 'size' ),
+				srcset = [];
+
+			// If the source was smaller than the size required by the theme, give the biggest we've got.
+			if ( ! attachment.sizes[ size ] ) {
+				size = 'full';
+			}
+
+			_.each( attachment.sizes, function( size ) {
+				srcset.push( size.url + ' ' + size.width + 'w' );
+			} );
+
+			$logo.attr( {
+				height: attachment.sizes[ size ].height,
+				width:  attachment.sizes[ size ].width,
+				src:    attachment.sizes[ size ].url,
+				srcset: srcset
+			} );
+
+			$( '.site-logo-link' ).show();
+			$( 'body' ).addClass( 'wp-site-logo' );
+		} );
+
+		api( 'site_logo', function( setting ) {
+			setting.bind( function( newValue ) {
+				if ( ! newValue ) {
+					$( '.site-logo-link' ).hide();
+					$( 'body' ).removeClass( 'wp-site-logo' );
+				}
+			} );
+		} );
+
 		api.trigger( 'preview-ready' );
 	});

Index: src/wp-includes/post-template.php
===================================================================
--- src/wp-includes/post-template.php	(revision 36507)
+++ src/wp-includes/post-template.php	(working copy)
@@ -706,6 +706,10 @@
 	if ( get_background_color() !== get_theme_support( 'custom-background', 'default-color' ) || get_background_image() )
 		$classes[] = 'custom-background';

+	if ( has_site_logo() ) {
+		$classes[] = 'wp-site-logo';
+	}
+
 	$page = $wp_query->get( 'page' );

 	if ( ! $page || $page < 2 )
