The following is a little ugly but it accounts for all birthdays that occur in the time span between start_date and end_date. If the time span is greater than a year then it basically returns everyone because everyone has a birthday somewhere in that time span. If it is less than a year but start and end are on different years it uses two between clauses to capture both the start time to the end of the year and the start of the year to the end time. This query also assumes that start_date <= end_date.
As long as you have an index on DAYOFYEAR( birthday ) this query is performant despite it's appearance.
SET @start_date = '2011-12-01';
SET @end_date = '2012-01-01';
SELECT * FROM users
WHERE DAYOFYEAR( birthday )
BETWEEN IF( YEAR( @end_date ) - YEAR( @start_date ) > 1, 1,
IF( YEAR( @end_date ) - YEAR( @start_date ) > 0,
IF( DAYOFYEAR( @start_date ) <= DAYOFYEAR( @end_date ), 1,
DAYOFYEAR( @start_date ) ),
DAYOFYEAR( @start_date ) ) )
AND IF( YEAR( @end_date ) - YEAR( @start_date ) > 1, 366,
IF( YEAR( @end_date ) - YEAR( @start_date ) > 0, 366,
DAYOFYEAR( @end_date ) ) )
OR DAYOFYEAR( birthday )
BETWEEN IF( YEAR( @end_date ) - YEAR( @start_date ) > 1, 1,
IF( YEAR( @end_date ) - YEAR( @start_date ) > 0, 1,
DAYOFYEAR( @start_date ) ) )
AND IF( YEAR( @end_date ) - YEAR( @start_date ) > 1, 366,
IF( YEAR( @end_date ) - YEAR( @start_date ) > 0,
IF( DAYOFYEAR( @start_date ) <= DAYOFYEAR( @end_date ), 366,
DAYOFYEAR( @end_date ) ),
DAYOFYEAR( @end_date ) ) );