parent
30775bc3e3
commit
141ac8df59
@ -6759,10 +6759,13 @@ int RM_InfoBeginDictField(RedisModuleInfoCtx *ctx, char *name) {
|
|||||||
/* Implicitly end dicts, instead of returning an error which is likely un checked. */
|
/* Implicitly end dicts, instead of returning an error which is likely un checked. */
|
||||||
if (ctx->in_dict_field)
|
if (ctx->in_dict_field)
|
||||||
RM_InfoEndDictField(ctx);
|
RM_InfoEndDictField(ctx);
|
||||||
|
char *tmpmodname, *tmpname;
|
||||||
ctx->info = sdscatfmt(ctx->info,
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
"%s_%s:",
|
"%s_%s:",
|
||||||
ctx->module->name,
|
getSafeInfoString(ctx->module->name, strlen(ctx->module->name), &tmpmodname),
|
||||||
name);
|
getSafeInfoString(name, strlen(name), &tmpname));
|
||||||
|
if (tmpmodname != NULL) zfree(tmpmodname);
|
||||||
|
if (tmpname != NULL) zfree(tmpname);
|
||||||
ctx->in_dict_field = 1;
|
ctx->in_dict_field = 1;
|
||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
27
src/server.c
27
src/server.c
@ -4501,6 +4501,25 @@ void bytesToHuman(char *s, unsigned long long n) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Characters we sanitize on INFO output to maintain expected format. */
|
||||||
|
static char unsafe_info_chars[] = "#:\n\r";
|
||||||
|
static char unsafe_info_chars_substs[] = "____"; /* Must be same length as above */
|
||||||
|
|
||||||
|
/* Returns a sanitized version of s that contains no unsafe info string chars.
|
||||||
|
* If no unsafe characters are found, simply returns s. Caller needs to
|
||||||
|
* free tmp if it is non-null on return.
|
||||||
|
*/
|
||||||
|
const char *getSafeInfoString(const char *s, size_t len, char **tmp) {
|
||||||
|
*tmp = NULL;
|
||||||
|
if (mempbrk(s, len, unsafe_info_chars,sizeof(unsafe_info_chars)-1)
|
||||||
|
== NULL) return s;
|
||||||
|
char *new = *tmp = zmalloc(len + 1);
|
||||||
|
memcpy(new, s, len);
|
||||||
|
new[len] = '\0';
|
||||||
|
return memmapchars(new, len, unsafe_info_chars, unsafe_info_chars_substs,
|
||||||
|
sizeof(unsafe_info_chars)-1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Create the string returned by the INFO command. This is decoupled
|
/* Create the string returned by the INFO command. This is decoupled
|
||||||
* by the INFO command itself as we need to report the same information
|
* by the INFO command itself as we need to report the same information
|
||||||
* on memory corruption problems. */
|
* on memory corruption problems. */
|
||||||
@ -5126,15 +5145,17 @@ sds genRedisInfoString(const char *section) {
|
|||||||
dictIterator *di;
|
dictIterator *di;
|
||||||
di = dictGetSafeIterator(server.commands);
|
di = dictGetSafeIterator(server.commands);
|
||||||
while((de = dictNext(di)) != NULL) {
|
while((de = dictNext(di)) != NULL) {
|
||||||
|
char *tmpsafe;
|
||||||
c = (struct redisCommand *) dictGetVal(de);
|
c = (struct redisCommand *) dictGetVal(de);
|
||||||
if (!c->calls && !c->failed_calls && !c->rejected_calls)
|
if (!c->calls && !c->failed_calls && !c->rejected_calls)
|
||||||
continue;
|
continue;
|
||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
"cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f"
|
"cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f"
|
||||||
",rejected_calls=%lld,failed_calls=%lld\r\n",
|
",rejected_calls=%lld,failed_calls=%lld\r\n",
|
||||||
c->name, c->calls, c->microseconds,
|
getSafeInfoString(c->name, strlen(c->name), &tmpsafe), c->calls, c->microseconds,
|
||||||
(c->calls == 0) ? 0 : ((float)c->microseconds/c->calls),
|
(c->calls == 0) ? 0 : ((float)c->microseconds/c->calls),
|
||||||
c->rejected_calls, c->failed_calls);
|
c->rejected_calls, c->failed_calls);
|
||||||
|
if (tmpsafe != NULL) zfree(tmpsafe);
|
||||||
}
|
}
|
||||||
dictReleaseIterator(di);
|
dictReleaseIterator(di);
|
||||||
}
|
}
|
||||||
@ -5147,10 +5168,12 @@ sds genRedisInfoString(const char *section) {
|
|||||||
raxSeek(&ri,"^",NULL,0);
|
raxSeek(&ri,"^",NULL,0);
|
||||||
struct redisError *e;
|
struct redisError *e;
|
||||||
while(raxNext(&ri)) {
|
while(raxNext(&ri)) {
|
||||||
|
char *tmpsafe;
|
||||||
e = (struct redisError *) ri.data;
|
e = (struct redisError *) ri.data;
|
||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
"errorstat_%.*s:count=%lld\r\n",
|
"errorstat_%.*s:count=%lld\r\n",
|
||||||
(int)ri.key_len, ri.key, e->count);
|
(int)ri.key_len, getSafeInfoString((char *) ri.key, ri.key_len, &tmpsafe), e->count);
|
||||||
|
if (tmpsafe != NULL) zfree(tmpsafe);
|
||||||
}
|
}
|
||||||
raxStop(&ri);
|
raxStop(&ri);
|
||||||
}
|
}
|
||||||
|
@ -2685,6 +2685,7 @@ void _serverPanic(const char *file, int line, const char *msg, ...);
|
|||||||
#endif
|
#endif
|
||||||
void serverLogObjectDebugInfo(const robj *o);
|
void serverLogObjectDebugInfo(const robj *o);
|
||||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
|
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
|
||||||
|
const char *getSafeInfoString(const char *s, size_t len, char **tmp);
|
||||||
sds genRedisInfoString(const char *section);
|
sds genRedisInfoString(const char *section);
|
||||||
sds genModulesInfoString(sds info);
|
sds genModulesInfoString(sds info);
|
||||||
void enableWatchdog(int period);
|
void enableWatchdog(int period);
|
||||||
|
27
src/util.c
27
src/util.c
@ -244,6 +244,33 @@ long long memtoll(const char *p, int *err) {
|
|||||||
return val*mul;
|
return val*mul;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Search a memory buffer for any set of bytes, like strpbrk().
|
||||||
|
* Returns pointer to first found char or NULL.
|
||||||
|
*/
|
||||||
|
const char *mempbrk(const char *s, size_t len, const char *chars, size_t charslen) {
|
||||||
|
for (size_t j = 0; j < len; j++) {
|
||||||
|
for (size_t n = 0; n < charslen; n++)
|
||||||
|
if (s[j] == chars[n]) return &s[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modify the buffer replacing all occurrences of chars from the 'from'
|
||||||
|
* set with the corresponding char in the 'to' set. Always returns s.
|
||||||
|
*/
|
||||||
|
char *memmapchars(char *s, size_t len, const char *from, const char *to, size_t setlen) {
|
||||||
|
for (size_t j = 0; j < len; j++) {
|
||||||
|
for (size_t i = 0; i < setlen; i++) {
|
||||||
|
if (s[j] == from[i]) {
|
||||||
|
s[j] = to[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
/* Return the number of digits of 'v' when converted to string in radix 10.
|
/* Return the number of digits of 'v' when converted to string in radix 10.
|
||||||
* See ll2string() for more information. */
|
* See ll2string() for more information. */
|
||||||
uint32_t digits10(uint64_t v) {
|
uint32_t digits10(uint64_t v) {
|
||||||
|
@ -49,6 +49,8 @@ int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase)
|
|||||||
int stringmatch(const char *p, const char *s, int nocase);
|
int stringmatch(const char *p, const char *s, int nocase);
|
||||||
int stringmatchlen_fuzz_test(void);
|
int stringmatchlen_fuzz_test(void);
|
||||||
long long memtoll(const char *p, int *err);
|
long long memtoll(const char *p, int *err);
|
||||||
|
const char *mempbrk(const char *s, size_t len, const char *chars, size_t charslen);
|
||||||
|
char *memmapchars(char *s, size_t len, const char *from, const char *to, size_t setlen);
|
||||||
uint32_t digits10(uint64_t v);
|
uint32_t digits10(uint64_t v);
|
||||||
uint32_t sdigits10(int64_t v);
|
uint32_t sdigits10(int64_t v);
|
||||||
int ll2string(char *s, size_t len, long long value);
|
int ll2string(char *s, size_t len, long long value);
|
||||||
|
@ -109,7 +109,7 @@ start_server {tags {"cli"}} {
|
|||||||
test_interactive_cli "INFO response should be printed raw" {
|
test_interactive_cli "INFO response should be printed raw" {
|
||||||
set lines [split [run_command $fd info] "\n"]
|
set lines [split [run_command $fd info] "\n"]
|
||||||
foreach line $lines {
|
foreach line $lines {
|
||||||
assert [regexp {^$|^#|^[a-z0-9_]+:.+} $line]
|
assert [regexp {^$|^#|^[^#:]+:} $line]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,11 @@ void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) {
|
|||||||
RedisModule_InfoAddFieldLongLong(ctx, "expires", 1);
|
RedisModule_InfoAddFieldLongLong(ctx, "expires", 1);
|
||||||
RedisModule_InfoEndDictField(ctx);
|
RedisModule_InfoEndDictField(ctx);
|
||||||
|
|
||||||
|
RedisModule_InfoAddSection(ctx, "unsafe");
|
||||||
|
RedisModule_InfoBeginDictField(ctx, "unsafe:field");
|
||||||
|
RedisModule_InfoAddFieldLongLong(ctx, "value", 1);
|
||||||
|
RedisModule_InfoEndDictField(ctx);
|
||||||
|
|
||||||
if (for_crash_report) {
|
if (for_crash_report) {
|
||||||
RedisModule_InfoAddSection(ctx, "Klingon");
|
RedisModule_InfoAddSection(ctx, "Klingon");
|
||||||
RedisModule_InfoAddFieldCString(ctx, "one", "wa’");
|
RedisModule_InfoAddFieldCString(ctx, "one", "wa’");
|
||||||
|
@ -150,4 +150,12 @@ start_server {tags {"info"}} {
|
|||||||
assert_match {} [errorstat NOPERM]
|
assert_match {} [errorstat NOPERM]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start_server {} {
|
||||||
|
test {Unsafe command names are sanitized in INFO output} {
|
||||||
|
catch {r host:} e
|
||||||
|
set info [r info commandstats]
|
||||||
|
assert_match {*cmdstat_host_:calls=1*} $info
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,5 +85,10 @@ start_server {tags {"modules"}} {
|
|||||||
set keys [scan [regexp -inline {keys\=([\d]*)} $keyspace] keys=%d]
|
set keys [scan [regexp -inline {keys\=([\d]*)} $keyspace] keys=%d]
|
||||||
} {3}
|
} {3}
|
||||||
|
|
||||||
|
test {module info unsafe fields} {
|
||||||
|
set info [r info infotest_unsafe]
|
||||||
|
assert_match {*infotest_unsafe_field:value=1*} $info
|
||||||
|
}
|
||||||
|
|
||||||
# TODO: test crash report.
|
# TODO: test crash report.
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user