maria-developers team mailing list archive
-
maria-developers team
-
Mailing list archive
-
Message #10915
MDEV-13972 crash in Item_func_sec_to_time::get_date
Hello Sergei,
Please review a patch for MDEV-13972.
The patch is for 5.5, as we agreed in slack.
Thanks!
commit 003c19c7677e1ce5b18b4081e21b7864700f8250
Author: Alexander Barkov <bar@xxxxxxxxxxx>
Date: Thu Oct 5 21:07:19 2017 +0400
MDEV-13972 crash in Item_func_sec_to_time::get_date
Item_func_sec_to_time::get_date() called a value method two times:
- once val_int() or val_decimal() inside Item::get_seconds()
- then val_str() to generate a warning, in case an out-of-range value
This approach was wrong for two reasons:
1. val_str() in some cases can return NULL even if val_int()/val_decimal()
previously returned a not-NULL value.
This caused the crash reported in the bug:
the warning generation code in Item_func_sec_to_time::get_date()
did not expect val_str() to return NULL.
2. Calling val_str() to generate warnings fires the function side effect again.
For example, consider SEC_TO_TIME(f1()), where f1() is a stored function
doing some INSERTs as a side effect. If f1() returns an out-of-range value,
the call for val_str() to generate the warning forces the INSERTs to happen
again, which is wrong. The side effect must happen only once per call.
Fixing the code not to do a dedicated call for val_str() for warnings.
To preserve the old warnings recorded in *.result files, four methods
where added:
- get_seconds_from_string()
- get_seconds_from_int()
- get_seconds_from_decimal()
- get_seconds_from_real()
A new structure Secondf_st was added:
- to avoid having too many arguments in get_seconds_from_xxx()
- to share common code, e.g. between double_to_datetime_with_warn()
and get_seconds_from_real()
- to share the code easier in the future
- to have the code in item.cc and item_timefunc.cc smaller
As a good side effect, a redundant warning in func_time.result disappeared
diff --git a/mysql-test/r/func_time.result b/mysql-test/r/func_time.result
index 68b1e0f..b46e7f3 100644
--- a/mysql-test/r/func_time.result
+++ b/mysql-test/r/func_time.result
@@ -1042,7 +1042,6 @@ SEC_TO_TIME(CAST(-1 AS UNSIGNED))
838:59:59
Warnings:
Note 1105 Cast to unsigned converted negative integer to it's positive complement
-Note 1105 Cast to unsigned converted negative integer to it's positive complement
Warning 1292 Truncated incorrect time value: '18446744073709551615'
SET NAMES latin1;
SET character_set_results = NULL;
@@ -2626,3 +2625,126 @@ DROP TABLE t1;
SELECT 1 MOD ADDTIME( '13:58:57', '00:00:01' ) + 2;
1 MOD ADDTIME( '13:58:57', '00:00:01' ) + 2
3
+#
+# MDEV-13972 crash in Item_func_sec_to_time::get_date
+#
+DO TO_DAYS(SEC_TO_TIME(TIME(CEILING(UUID())))) /*sporadic warnings*/;
+DO TO_DAYS(SEC_TO_TIME(MAKEDATE('',RAND(~('')))));
+Warnings:
+Warning 1292 Truncated incorrect INTEGER value: ''
+Warning 1292 Truncated incorrect INTEGER value: ''
+Warning 1292 Truncated incorrect time value: '20000101'
+SELECT TO_DAYS(SEC_TO_TIME(MAKEDATE(0,RAND(~0))));
+TO_DAYS(SEC_TO_TIME(MAKEDATE(0,RAND(~0))))
+0
+Warnings:
+Warning 1292 Truncated incorrect time value: '20000101'
+SELECT SEC_TO_TIME(MAKEDATE(0,RAND(~0)));
+SEC_TO_TIME(MAKEDATE(0,RAND(~0)))
+838:59:59
+Warnings:
+Warning 1292 Truncated incorrect time value: '20000101'
+SELECT SEC_TO_TIME(3020400);
+SEC_TO_TIME(3020400)
+838:59:59
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400'
+SELECT SEC_TO_TIME(3020400e0);
+SEC_TO_TIME(3020400e0)
+838:59:59.999999
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400'
+SELECT SEC_TO_TIME(3020400.0);
+SEC_TO_TIME(3020400.0)
+838:59:59.9
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400.0'
+SELECT SEC_TO_TIME(3020400.00);
+SEC_TO_TIME(3020400.00)
+838:59:59.99
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400.00'
+SELECT SEC_TO_TIME(3020400.000);
+SEC_TO_TIME(3020400.000)
+838:59:59.999
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400.000'
+SELECT SEC_TO_TIME(3020400.0000);
+SEC_TO_TIME(3020400.0000)
+838:59:59.9999
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400.0000'
+SELECT SEC_TO_TIME(3020400.00000);
+SEC_TO_TIME(3020400.00000)
+838:59:59.99999
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400.00000'
+SELECT SEC_TO_TIME(3020400.000000);
+SEC_TO_TIME(3020400.000000)
+838:59:59.999999
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400.000000'
+SELECT SEC_TO_TIME(3020400.0000000);
+SEC_TO_TIME(3020400.0000000)
+838:59:59.999999
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400.0000000'
+SELECT SEC_TO_TIME('3020400');
+SEC_TO_TIME('3020400')
+838:59:59.999999
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400'
+SELECT SEC_TO_TIME(TIME(3020400));
+SEC_TO_TIME(TIME(3020400))
+838:59:59
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400'
+SELECT SEC_TO_TIME(TIME('302:04:00'));
+SEC_TO_TIME(TIME('302:04:00'))
+838:59:59
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400'
+SELECT SEC_TO_TIME(TIMESTAMP('0000-00-03 02:04:00'));
+SEC_TO_TIME(TIMESTAMP('0000-00-03 02:04:00'))
+838:59:59
+Warnings:
+Warning 1292 Truncated incorrect time value: '3020400'
+SELECT SEC_TO_TIME(3020399);
+SEC_TO_TIME(3020399)
+838:59:59
+SELECT SEC_TO_TIME(3020399e0);
+SEC_TO_TIME(3020399e0)
+838:59:59
+SELECT SEC_TO_TIME(3020399.0);
+SEC_TO_TIME(3020399.0)
+838:59:59.0
+SELECT SEC_TO_TIME(3020399.00);
+SEC_TO_TIME(3020399.00)
+838:59:59.00
+SELECT SEC_TO_TIME(3020399.000);
+SEC_TO_TIME(3020399.000)
+838:59:59.000
+SELECT SEC_TO_TIME(3020399.0000);
+SEC_TO_TIME(3020399.0000)
+838:59:59.0000
+SELECT SEC_TO_TIME(3020399.00000);
+SEC_TO_TIME(3020399.00000)
+838:59:59.00000
+SELECT SEC_TO_TIME(3020399.000000);
+SEC_TO_TIME(3020399.000000)
+838:59:59.000000
+SELECT SEC_TO_TIME(3020399.0000000);
+SEC_TO_TIME(3020399.0000000)
+838:59:59.000000
+SELECT SEC_TO_TIME('3020399');
+SEC_TO_TIME('3020399')
+838:59:59
+SELECT SEC_TO_TIME(TIME(3020359));
+SEC_TO_TIME(TIME(3020359))
+838:59:19
+SELECT SEC_TO_TIME(TIME('302:03:59'));
+SEC_TO_TIME(TIME('302:03:59'))
+838:59:19
+SELECT SEC_TO_TIME(TIMESTAMP('0000-00-03 02:03:59'));
+SEC_TO_TIME(TIMESTAMP('0000-00-03 02:03:59'))
+838:59:19
diff --git a/mysql-test/t/func_time.test b/mysql-test/t/func_time.test
index 92e1c38..dc6b747 100644
--- a/mysql-test/t/func_time.test
+++ b/mysql-test/t/func_time.test
@@ -1602,3 +1602,44 @@ DROP TABLE t1;
--echo # MDEV-10524 Assertion `arg1_int >= 0' failed in Item_func_additive_op::result_precision()
--echo #
SELECT 1 MOD ADDTIME( '13:58:57', '00:00:01' ) + 2;
+
+--echo #
+--echo # MDEV-13972 crash in Item_func_sec_to_time::get_date
+--echo #
+
+--disable_warnings
+DO TO_DAYS(SEC_TO_TIME(TIME(CEILING(UUID())))) /*sporadic warnings*/;
+--enable_warnings
+DO TO_DAYS(SEC_TO_TIME(MAKEDATE('',RAND(~('')))));
+SELECT TO_DAYS(SEC_TO_TIME(MAKEDATE(0,RAND(~0))));
+SELECT SEC_TO_TIME(MAKEDATE(0,RAND(~0)));
+#
+# TIME_MAX_VALUE_SECONDS = 838*3600+59*60+59 = 3020400
+#
+SELECT SEC_TO_TIME(3020400);
+SELECT SEC_TO_TIME(3020400e0);
+SELECT SEC_TO_TIME(3020400.0);
+SELECT SEC_TO_TIME(3020400.00);
+SELECT SEC_TO_TIME(3020400.000);
+SELECT SEC_TO_TIME(3020400.0000);
+SELECT SEC_TO_TIME(3020400.00000);
+SELECT SEC_TO_TIME(3020400.000000);
+SELECT SEC_TO_TIME(3020400.0000000);
+SELECT SEC_TO_TIME('3020400');
+SELECT SEC_TO_TIME(TIME(3020400));
+SELECT SEC_TO_TIME(TIME('302:04:00'));
+SELECT SEC_TO_TIME(TIMESTAMP('0000-00-03 02:04:00'));
+
+SELECT SEC_TO_TIME(3020399);
+SELECT SEC_TO_TIME(3020399e0);
+SELECT SEC_TO_TIME(3020399.0);
+SELECT SEC_TO_TIME(3020399.00);
+SELECT SEC_TO_TIME(3020399.000);
+SELECT SEC_TO_TIME(3020399.0000);
+SELECT SEC_TO_TIME(3020399.00000);
+SELECT SEC_TO_TIME(3020399.000000);
+SELECT SEC_TO_TIME(3020399.0000000);
+SELECT SEC_TO_TIME('3020399');
+SELECT SEC_TO_TIME(TIME(3020359));
+SELECT SEC_TO_TIME(TIME('302:03:59'));
+SELECT SEC_TO_TIME(TIMESTAMP('0000-00-03 02:03:59'));
diff --git a/sql/item.cc b/sql/item.cc
index fdfbba3..73a1acd 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -1407,22 +1407,82 @@ bool Item::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate)
return null_value|= !(fuzzydate & TIME_FUZZY_DATES);
}
-bool Item::get_seconds(ulonglong *sec, ulong *sec_part)
+
+bool Item::get_seconds(Secondf_st *sec, const char *type, ulonglong maxsec)
{
- if (decimals == 0)
- { // optimize for an important special case
- longlong val= val_int();
- bool neg= val < 0 && !unsigned_flag;
- *sec= neg ? -val : val;
- *sec_part= 0;
- return neg;
+ switch (cmp_type()) {
+ case STRING_RESULT:
+ return get_seconds_from_string(sec, type, maxsec);
+ case TIME_RESULT:
+ case DECIMAL_RESULT:
+ if (decimals == 0) // optimize for an important special case
+ return get_seconds_from_int(sec, type, maxsec);
+ return get_seconds_from_decimal(sec, type, maxsec);
+ case INT_RESULT:
+ return get_seconds_from_int(sec, type, maxsec);
+ case REAL_RESULT:
+ return get_seconds_from_real(sec, type, maxsec);
+ case ROW_RESULT:
+ case IMPOSSIBLE_RESULT:
+ break;
}
+ DBUG_ASSERT(0);
+ sec->init();
+ return 0;
+}
+
+
+bool Item::get_seconds_from_int(Secondf_st *sec,
+ const char *type, ulonglong maxsec)
+{
+ longlong val= val_int();
+ sec->set(val, unsigned_flag);
+ sec->check_range_with_warn(type, maxsec, ErrConvInteger(val, unsigned_flag));
+ return null_value;
+}
+
+
+bool Item::get_seconds_from_real(Secondf_st *sec,
+ const char *type, ulonglong maxsec)
+{
+ double val= val_real();
+ sec->set(val);
+ sec->check_range_with_warn(type, maxsec, ErrConvDouble(val));
+ return null_value;
+}
+
+
+bool Item::get_seconds_from_decimal(Secondf_st *sec,
+ const char *type, ulonglong maxsec)
+{
my_decimal tmp, *dec= val_decimal(&tmp);
if (!dec)
- return 0;
- return my_decimal2seconds(dec, sec, sec_part);
+ {
+ sec->init();
+ return true;
+ }
+ sec->set(dec);
+ sec->check_range_with_warn(type, maxsec, ErrConvDecimal(dec));
+ return false;
}
+
+bool Item::get_seconds_from_string(Secondf_st *sec,
+ const char *type, ulonglong maxsec)
+{
+ char buff[64];
+ String tmp(buff, sizeof(buff), &my_charset_bin), *res= val_str(&tmp);
+ if (!res)
+ {
+ sec->init();
+ return true;
+ }
+ sec->set(res);
+ sec->check_range_with_warn(type, maxsec, ErrConvString(res));
+ return false;
+}
+
+
CHARSET_INFO *Item::default_charset()
{
return current_thd->variables.collation_connection;
diff --git a/sql/item.h b/sql/item.h
index 9db5c7e..86883bd 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -28,6 +28,7 @@
#include "unireg.h" // REQUIRED: for other includes
#include "thr_malloc.h" /* sql_calloc */
#include "field.h" /* Derivation */
+#include "sql_time.h" /* Secondf_st */
C_MODE_START
#include <ma_dyncol.h>
@@ -1098,7 +1099,18 @@ class Item {
virtual bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate);
bool get_time(MYSQL_TIME *ltime)
{ return get_date(ltime, TIME_TIME_ONLY); }
- bool get_seconds(ulonglong *sec, ulong *sec_part);
+ /*
+ Get a signed SECOND+MICROSECOND value:
+ @param sec - the value will be written here
+ @param type - the data type name for warnings
+ @param maxsec - if the SECOND value is greater than maxsec, issue a warning
+ @retval - false on success, true on NULL
+ */
+ bool get_seconds(Secondf_st *sec, const char *type, ulonglong maxsec);
+ bool get_seconds_from_int(Secondf_st *sec, const char *type, ulonglong maxsec);
+ bool get_seconds_from_string(Secondf_st *sec, const char *type, ulonglong maxsec);
+ bool get_seconds_from_decimal(Secondf_st *sec, const char *type, ulonglong maxsec);
+ bool get_seconds_from_real(Secondf_st *sec, const char *type, ulonglong maxsec);
virtual bool get_date_result(MYSQL_TIME *ltime, ulonglong fuzzydate)
{ return get_date(ltime,fuzzydate); }
/*
diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc
index 0ed1506..72c3e95 100644
--- a/sql/item_timefunc.cc
+++ b/sql/item_timefunc.cc
@@ -1700,42 +1700,33 @@ bool Item_func_sysdate_local::get_date(MYSQL_TIME *res,
bool Item_func_sec_to_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
{
DBUG_ASSERT(fixed == 1);
- bool sign;
- ulonglong sec;
- ulong sec_part;
+ Secondf_st sec;
bzero((char *)ltime, sizeof(*ltime));
ltime->time_type= MYSQL_TIMESTAMP_TIME;
- sign= args[0]->get_seconds(&sec, &sec_part);
-
- if ((null_value= args[0]->null_value))
- return 1;
+ if ((null_value= (args[0]->get_seconds(&sec,
+ "time", TIME_MAX_VALUE_SECONDS))))
+ return true;
- ltime->neg= sign;
- if (sec > TIME_MAX_VALUE_SECONDS)
+ ltime->neg= sec.m_neg;
+ if (sec.m_second > TIME_MAX_VALUE_SECONDS)
goto overflow;
- DBUG_ASSERT(sec_part <= TIME_MAX_SECOND_PART);
+ DBUG_ASSERT(sec.m_usecond <= TIME_MAX_SECOND_PART);
- ltime->hour= (uint) (sec/3600);
- ltime->minute= (uint) (sec % 3600) /60;
- ltime->second= (uint) sec % 60;
- ltime->second_part= sec_part;
+ ltime->hour= (uint) (sec.m_second / 3600);
+ ltime->minute= (uint) (sec.m_second % 3600) /60;
+ ltime->second= (uint) sec.m_second % 60;
+ ltime->second_part= sec.m_usecond;
return 0;
overflow:
/* use check_time_range() to set ltime to the max value depending on dec */
int unused;
- char buf[100];
- String tmp(buf, sizeof(buf), &my_charset_bin), *err= args[0]->val_str(&tmp);
-
ltime->hour= TIME_MAX_HOUR+1;
check_time_range(ltime, decimals, &unused);
- make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- err->ptr(), err->length(),
- MYSQL_TIMESTAMP_TIME, NullS);
return 0;
}
@@ -1929,21 +1920,18 @@ void Item_func_from_unixtime::fix_length_and_dec()
bool Item_func_from_unixtime::get_date(MYSQL_TIME *ltime,
ulonglong fuzzy_date __attribute__((unused)))
{
- bool sign;
- ulonglong sec;
- ulong sec_part;
+ Secondf_st sec;
bzero((char *)ltime, sizeof(*ltime));
ltime->time_type= MYSQL_TIMESTAMP_TIME;
- sign= args[0]->get_seconds(&sec, &sec_part);
-
- if (args[0]->null_value || sign || sec > TIMESTAMP_MAX_VALUE)
+ if (args[0]->get_seconds(&sec, "", ULONGLONG_MAX) ||
+ sec.m_neg || sec.m_second > TIMESTAMP_MAX_VALUE)
return (null_value= 1);
- tz->gmt_sec_to_TIME(ltime, (my_time_t)sec);
+ tz->gmt_sec_to_TIME(ltime, (my_time_t) sec.m_second);
- ltime->second_part= sec_part;
+ ltime->second_part= sec.m_usecond;
return (null_value= 0);
}
@@ -2735,12 +2723,11 @@ bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
bool overflow= 0;
longlong hour= args[0]->val_int();
longlong minute= args[1]->val_int();
- ulonglong second;
- ulong microsecond;
- bool neg= args[2]->get_seconds(&second, µsecond);
+ Secondf_st sec;
- if (args[0]->null_value || args[1]->null_value || args[2]->null_value ||
- minute < 0 || minute > 59 || neg || second > 59)
+ if (args[0]->null_value || args[1]->null_value ||
+ args[2]->get_seconds(&sec, "", ULONGLONG_MAX) ||
+ minute < 0 || minute > 59 || sec.m_neg || sec.m_second > 59)
return (null_value= 1);
bzero(ltime, sizeof(*ltime));
@@ -2761,8 +2748,8 @@ bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
{
ltime->hour= (uint) ((hour < 0 ? -hour : hour));
ltime->minute= (uint) minute;
- ltime->second= (uint) second;
- ltime->second_part= microsecond;
+ ltime->second= (uint) sec.m_second;
+ ltime->second_part= sec.m_usecond;
}
else
{
@@ -2771,7 +2758,8 @@ bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
ltime->second= TIME_MAX_SECOND;
char buf[28];
char *ptr= longlong10_to_str(hour, buf, args[0]->unsigned_flag ? 10 : -10);
- int len = (int)(ptr - buf) + sprintf(ptr, ":%02u:%02u", (uint)minute, (uint)second);
+ int len= (int)(ptr - buf) + sprintf(ptr, ":%02u:%02u",
+ (uint) minute, (uint) sec.m_second);
make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
buf, len, MYSQL_TIMESTAMP_TIME,
NullS);
diff --git a/sql/sql_time.cc b/sql/sql_time.cc
index d912a7b..fc3c644 100644
--- a/sql/sql_time.cc
+++ b/sql/sql_time.cc
@@ -27,6 +27,44 @@
#define MAX_DAY_NUMBER 3652424L
+
+void Secondf_st::set(double value)
+{
+ if ((m_neg= value < 0))
+ value= -value;
+ if (value > LONGLONG_MAX)
+ value= static_cast<double>(LONGLONG_MAX);
+ m_second= static_cast<ulonglong>(floor(value));
+ m_usecond= static_cast<ulong>((value - floor(value))*TIME_SECOND_PART_FACTOR);
+}
+
+
+void Secondf_st::set(const my_decimal *dec)
+{
+ m_neg= my_decimal2seconds(dec, &m_second, &m_usecond);
+}
+
+
+void Secondf_st::set(const String *str)
+{
+ my_decimal dec;
+ (void) str2my_decimal(E_DEC_FATAL_ERROR,
+ str->ptr(), str->length(), str->charset(), &dec);
+ set(&dec);
+}
+
+
+void Secondf_st::check_range_with_warn(const char *type,
+ ulonglong maxsec,
+ const ErrConv &err)
+{
+ if (m_second > maxsec)
+ push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_TRUNCATED_WRONG_VALUE, ER(ER_TRUNCATED_WRONG_VALUE),
+ type, err.ptr());
+}
+
+
/* Some functions to calculate dates */
/*
@@ -399,18 +437,10 @@ bool double_to_datetime_with_warn(double value, MYSQL_TIME *ltime,
ulonglong fuzzydate, const char *field_name)
{
const ErrConvDouble str(value);
- bool neg= value < 0;
-
- if (neg)
- value= -value;
-
- if (value > LONGLONG_MAX)
- value= static_cast<double>(LONGLONG_MAX);
-
- longlong nr= static_cast<ulonglong>(floor(value));
- uint sec_part= static_cast<ulong>((value - floor(value))*TIME_SECOND_PART_FACTOR);
- return number_to_time_with_warn(neg, nr, sec_part, ltime, fuzzydate, &str,
- field_name);
+ Secondf_st sec2;
+ sec2.set(value);
+ return number_to_time_with_warn(sec2.m_neg, sec2.m_second, sec2.m_usecond,
+ ltime, fuzzydate, &str, field_name);
}
diff --git a/sql/sql_time.h b/sql/sql_time.h
index 9becdcd..4a6307d 100644
--- a/sql/sql_time.h
+++ b/sql/sql_time.h
@@ -32,6 +32,33 @@ typedef struct st_known_date_time_format KNOWN_DATE_TIME_FORMAT;
#define WEEK_YEAR 2
#define WEEK_FIRST_WEEKDAY 4
+
+struct Secondf_st
+{
+public:
+ ulonglong m_second;
+ ulong m_usecond;
+ bool m_neg;
+ void init()
+ {
+ m_neg= false;
+ m_second= 0;
+ m_usecond= 0;
+ }
+ void set(double nr);
+ void set(longlong val, bool unsigned_flag)
+ {
+ m_neg= val < 0 && !unsigned_flag;
+ m_second= m_neg ? -val : val;
+ m_usecond= 0;
+ }
+ void set(const my_decimal *dec);
+ void set(const String *str);
+ void check_range_with_warn(const char *type, ulonglong maxsec,
+ const ErrConv &err);
+};
+
+
ulong convert_period_to_month(ulong period);
ulong convert_month_to_period(ulong month);
bool time_to_datetime(MYSQL_TIME *ltime);
Follow ups