antirez / kilo Goto Github PK
View Code? Open in Web Editor NEWA text editor in less than 1000 LOC with syntax highlight and search.
License: BSD 2-Clause "Simplified" License
A text editor in less than 1000 LOC with syntax highlight and search.
License: BSD 2-Clause "Simplified" License
I have an IBM 3161 ASCII terminal. The point of using curses is to make your programs terminal-independent. I think it'd be a very good idea to add it (though I understand this may push the 'lines of code' up a lot).
If your intention is portability, curses is the way to go. VT100 codes are not universal, although they are common. The 3161 doesn't emulate a VT100 either; it emulates the ADDS Viewpoint, ADM-3A, ADM-5, Hazeltine 1500, IBM 3101, and TeleVideo 910. None of those are VT100 compatible.
Alternately, a method to manually supply alternate escape codes would be good.
There is a heap overflow caused by integer overflow in kilo.c.
POC:
python -c "print '\t'*477218598" > ./exp
In command line:
make CC="clang-4.0 -fsanitize=address"
./kilo ./exp
Output:
=================================================================
==18601==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x608000000077 at pc 0x00000050f641 bp 0x7ffd0126fe50 sp 0x7ffd0126fe48
WRITE of size 1 at 0x608000000077 thread T0
#0 0x50f640 (/home/kirin/kilo/kilo+0x50f640)
#1 0x50fde0 (/home/kirin/kilo/kilo+0x50fde0)
#2 0x511ae0 (/home/kirin/kilo/kilo+0x511ae0)
#3 0x514833 (/home/kirin/kilo/kilo+0x514833)
#4 0x7f99a53a0b96 (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
#5 0x41c339 (/home/kirin/kilo/kilo+0x41c339)
0x608000000077 is located 0 bytes to the right of 87-byte region [0x608000000020,0x608000000077)
allocated by thread T0 here:
#0 0x4d1990 (/home/kirin/kilo/kilo+0x4d1990)
#1 0x50f45e (/home/kirin/kilo/kilo+0x50f45e)
#2 0x50fde0 (/home/kirin/kilo/kilo+0x50fde0)
#3 0x511ae0 (/home/kirin/kilo/kilo+0x511ae0)
#4 0x514833 (/home/kirin/kilo/kilo+0x514833)
#5 0x7f99a53a0b96 (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/kirin/kilo/kilo+0x50f640)
Shadow bytes around the buggy address:
0x0c107fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c107fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c107fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c107fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c107fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c107fff8000: fa fa fa fa 00 00 00 00 00 00 00 00 00 00[07]fa
0x0c107fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c107fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c107fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c107fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c107fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==18601==ABORTING
Analyze:
There is an integer overflow in function editorUpdateRow:
for (j = 0; j < row->size; j++)
if (row->chars[j] == TAB) tabs++;
row->render = malloc(row->size + tabs*8 + nonprint*9 + 1);
idx = 0;
for (j = 0; j < row->size; j++) {
if (row->chars[j] == TAB) {
row->render[idx++] = ' ';
......
The space size being malloc will be calculated based on the number of TABs in one row.
When the number of TAB is too big,it will lead to Integer Overflow. And it will lead to heap-buffer-overflow finally.
Given the following text:
erow *row = &E.row[i];
free(row->chars);
Running find with the search-term row
only matches the first occurrence of row
in the first line, such that pressing right
moves to the second line, rather than the second match.
Not a huge deal, but might be nice to make a note of it.
Fixing is easy enough, but involves iterating over every possible character in the line, and testing that position. Something like:
for (x = 0; x < E.row[current].rsize; x++ ) {
match = strstr(E.row[current].render + x , query);
if ( match == 0 ) {
// got a match at column-position "x"
}
}
However this will be grossly inefficient.
I'm following your blog on building a text editor. btw Thank you so much for that nice blog.
when I call getCursorposition to query the window size it stores the window size to the buf variable noramly but also print the escape sequence to the terminal and waits for me to entire a key before it refresh the screen and display the ~
rows.
I tired to clear the screen after reading the window size but it't not working.
I also cloned down your repo to test it and you have the same issue.
my question is, why is that happinging and how to fix it?
Thank you
This program may not be used on Windows , e.g. terminos.h.
Do you plan to support running on Windows?
to reproduce, start a new line, hit TAB
, then /
twice.
or hit /
twice and then TAB
at the beginning of the line.
Segmentation fault: 11
Seems like the while(*p)
loop in editorUpdateSyntax
has some bug.
dcarol@dcarol ~/w/g/kilo> make
cc -o kilo kilo.c -Wall -W -pedantic -std=c99
In file included from /usr/include/termios.h:25:0,
from kilo.c:40:
/usr/include/features.h:148:3: warning: #warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE" [-Wcpp]
# warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
^~~~~~~
kilo.c: In function ‘editorRefreshScreen’:
kilo.c:953:19: warning: implicit declaration of function ‘time’ [-Wimplicit-function-declaration]
if (msglen && time(NULL)-E.statusmsg_time < 5)
^~~~
Kilo seems to be acting oddly with iTerm2 shell integration. When working properly, a small blue triangle appears next to each new prompt:
(screenshot from iTerm2 documentation)
When using Kilo:
editorSyntax->flags
is never tested for HL_HIGHLIGHT_STRINGS
or HL_HIGHLIGHT_NUMBERS
those settings are always assumed.
This commit adds testing those flags before updating the highlight-code.
Vim and others appear to do this. It also has the added benefit of fixing Page Up
and Page Down
on MacOS using Terminal.app.
void initEditor() {
write(STDOUT_FILENO, "\033[?47h", 6); // New (smcup)
EditorConfig.cursor.x = 0;
EditorConfig.cursor.y = 0;
if (getWindowSize(&EditorConfig.screenRows, &EditorConfig.screenCols) == -1) die("getWindowSize");
terminalClearScreen();
}
// ...
void disableRawMode() {
write(STDOUT_FILENO, "\033[?47l", 6); // New (rmcup)
tcsetattr(STDIN_FILENO, TCSAFLUSH, &EditorConfig.orig_termios);
}
More info here.
I tried to debug the program using GDB and VS Code, but since the code uses escape sequences on Terminal for display, it can interfere with GDB output. For example, when a program calls write(1, "\x1b[6n" , 4)
GDB will just hang there and not continue. How can I debug in this case? Thanks!
*.c
file with 4 empty lines.kilo
, go to the 3rd line, press delete twicea
#81 resolves this, please consider merging it.
kilo.c:71:8: warning: padding size of 'struct editorSyntax' with 4 bytes to alignment boundary [-Wpadded]
struct editorSyntax {
^
kilo.c:106:11: warning: padding struct 'struct editorConfig' with 4 bytes to align 'filename' [-Wpadded]
char *filename; /* Currently open filename */
kilo.c:85:11: warning: padding struct 'struct erow' with 4 bytes to align 'chars' [-Wpadded]
char *chars; /* Row content. */
^
kilo.c:81:16: warning: padding size of 'struct erow' with 4 bytes to alignment boundary [-Wpadded]
typedef struct erow {
kilo.c:229:20: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
kilo.c:231:20: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
raw.c_oflag &= ~(OPOST);
kilo.c:256:21: warning: implicit conversion loses integer precision: 'ssize_t' (aka 'long') to 'int' [-Wshorten-64-to-32]
while ((nread = read(fd,&c,1)) == 0);
kilo.c:383:36: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
row->hl = realloc(row->hl,row->rsize);
~~~~~~~ ~~~~~^~~~~
kilo.c:485:28: warning: implicit conversion loses integer precision: 'unsigned long' to 'int' [-Wshorten-64-to-32]
int klen = strlen(keywords[j]);
kilo.c:489:43: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
if (!memcmp(p,keywords[j],klen) &&
kilo.c:541:26: warning: implicit conversion loses integer precision: 'unsigned long' to 'int' [-Wshorten-64-to-32]
int patlen = strlen(s->filematch[i]);
~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~
kilo.c:573:31: warning: implicit conversion changes signedness: 'int' to 'unsigned int' [-Wsign-conversion]
row->render = malloc(row->size + tabs*8 + nonprint*9 + 1);
kilo.c:594:50: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
E.row = realloc(E.row,sizeof(erow)*(E.numrows+1));
~ ~~~~~~~~~^~
kilo.c:596:64: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
memmove(E.row+at+1,E.row+at,sizeof(E.row[0])*(E.numrows-at));
kilo.c:599:22: warning: implicit conversion loses integer precision: 'size_t' (aka 'unsigned long') to 'int' [-Wshorten-64-to-32]
E.row[at].size = len;
kilo.c:627:63: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
memmove(E.row+at,E.row+at+1,sizeof(E.row[0])*(E.numrows-at-1));
kilo.c:648:22: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
p = buf = malloc(totlen);
kilo.c:667:57: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
row->chars = realloc(row->chars,row->size+padlen+2);
~~~~~~~ ~~~~~~~~~~~~~~~~^~
kilo.c:674:50: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
row->chars = realloc(row->chars,row->size+2);
~~~~~~~ ~~~~~~~~~^~
kilo.c:678:22: warning: implicit conversion loses integer precision: 'int' to 'char' [-Wimplicit-int-conversion]
row->chars[at] = c;
kilo.c:685:42: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
row->chars = realloc(row->chars,row->size+len+1);
kilo.c:744:63: warning: implicit conversion changes signedness: 'int' to 'size_t' (aka 'unsigned long') [-Wsign-conversion]
editorInsertRow(filerow+1,row->chars+filecol,row->size-filecol);
kilo.c:771:65: warning: implicit conversion changes signedness: 'int' to 'size_t' (aka 'unsigned long') [-Wsign-conversion]
editorRowAppendString(&E.row[filerow-1],row->chars,row->size);
kilo.c:821:40: warning: implicit conversion changes signedness: 'ssize_t' (aka 'long') to 'size_t' (aka 'unsigned long') [-Wsign-conversion]
editorInsertRow(E.numrows,line,linelen);
~~~~~~~~~~~~~~~ ^~~~~~~
kilo.c:839:22: warning: implicit conversion changes signedness: 'int' to 'size_t' (aka 'unsigned long') [-Wsign-conversion]
if (write(fd,buf,len) != len) goto writeerr;
~~~~~ ^~~
kilo.c:868:38: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
char *new = realloc(ab->b,ab->len+len);
~~~~~~~ ~~~~~~~^~~~
kilo.c:860:8: warning: padding size of 'struct abuf' with 4 bytes to alignment boundary [-Wpadded]
struct abuf {
^
kilo.c:939:30: warning: declaration shadows a local variable [-Wshadow]
char buf[16];
^
kilo.c:885:10: note: previous declaration is here
char buf[32];
^
kilo.c:996:33: warning: implicit conversion changes signedness: 'int' to 'size_t' (aka 'unsigned long') [-Wsign-conversion]
write(STDOUT_FILENO,ab.b,ab.len);
~~~~~ ~~~^~~
kilo.c:976:18: warning: implicit conversion loses integer precision: 'unsigned long' to 'int' [-Wshorten-64-to-32]
int msglen = strlen(E.statusmsg);
~~~~~~ ^~~~~~~~~~~~~~~~~~~
kilo.c:994:22: warning: implicit conversion loses integer precision: 'unsigned long' to 'int' [-Wshorten-64-to-32]
abAppend(&ab,buf,strlen(buf));
~~~~~~~~ ^~~~~~~~~~~
kilo.c:1005:47: warning: format string is not a string literal [-Wformat-nonliteral]
vsnprintf(E.statusmsg,sizeof(E.statusmsg),fmt,ap);
^~~
kilo.c:1057:33: warning: implicit conversion loses integer precision: 'int' to 'char' [-Wimplicit-int-conversion]
query[qlen++] = c;
~ ^
kilo.c:1090:44: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
saved_hl = malloc(row->rsize);
~~~~~~ ~~~~~^~~~~
kilo.c:1076:41: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32]
match_offset = match-E.row[current].render;
~ ~~~~~^~~~~~~~~~~~~~~~~~~~~~
kilo.c:1211:9: warning: 'break' will never be executed [-Wunreachable-code-break]
break;
kilo.c:1307:12: warning: 'return' will never be executed [-Wunreachable-code-return]
return 0;
Not sure if you ever have the time, but in the distant future perhaps you could either add
to the main README, or to another file, and explain the code as well on a semi-high
level? Just as an entry point and starter, does NOT have to be complete, just something
that gives an overview, and adds a bit more to that overview. Thank you for reading.
I've created a fork which pushes a lot of the functionality, including loading syntax highlighting, to external lua script. One obvious thing I wanted to do was to define syntax highlighting for lua, which I've done. Unfortunately this breaks the implicit assumption that has been made regarding single-line vs. multi-line comment handling.
In C/C++ comments are like this:
// Single line.
/* Multi-line */
Each token is distinct; //
vs. /*
vs */
. However in Lua that is not true:
-- Single-line
--[[ Multi
LIne --]]
kilo
doesn't support this correctly, because when scanning a line the case of the single-line comment is handled first, which means --[[ Foo
is treated as a single-line comment, rather than as the opening of a multi-line comment.
My fork makes this obvious pretty quickly. To demonstrate this in pure-kilo though you'll need to define a new syntax table entry with contents like this:
char *LUA_HL_extensions[] = {".lua",NULL};
char *LUA_HL_keywords[] = {
"and", "break", "do", "else", "elseif", "end", "false","for", "function", "if", "in", "local", "nil", "not","or", "repeat", "return", "then", "true", "until", "while", NULL
};
Then add that to the struct editorSyntax HLDB[]
definition:
{
/* Lua */
LUA_HL_extensions,
LUA_HL_keywords,
"--","--[[","--]]",
HL_HIGHLIGHT_STRINGS | HL_HIGHLIGHT_NUMBERS
}
Once present the following file test.lua
can be used to test:
function foo()
-- This is a comment
end
--[[ This function should be 100% commented - and isn't
function bar()
..
end
--]]
Even though exposed in my fork I believe this is a general bug, which is worthy of being reported here. Apologies for the lack of fix. My initial attempt was to move the handling of single-line comments after the string/multi-line comment handling, but that naive approach didn't work. For the moment I'm pretending the problem doesn't exist ;)
This project looks ideal for my needs.
The complication is I would like to import it into a project with a GPL License.
This is not a religious argument about licenses, just a practical request. Would it be possible for the original author to give this a dual License?
Thanks.
Jon
Hi. Awesome project!
I always wanted to learn, how does textminal/text magic work, and this code is fantastic. But I can't understand the editorRefreshScreen
function. How doest it render text and doesn't just append it to terminal? What is the idea behind it?
Can someone give me a hint?
The function getCursorPosition()
use escape sequence to query cursor position. But after write()
, the result will block this process. So we should use pipe
or other ways to redirect to suppress output.
if kilo is compiled with the musl gcc wrapper, the following will trigger a segfault
> kilo new_file.c
then enter on first line:
puts("\e
This will trigger a segfault in musl (but not glibc)
The following will fix the problem - change line 445 from:
if (*p == '\\') {
To:
if (*p == '\\' && *(p+1)) {
Strings in comments & viceversa are incorrectly highlighted.
Quick fix might be:
When handling comments check first if we're not in string:
if (!in_string) { /* Handle multi line comments. */
Same for strings:
if (!in_comment) { /* Handle "" and '' */
In editorUpdateSyntax.
Will make a PR if you want but in a few hours.
just for the records:
Fedora release 29
gcc (GCC) 8.2.1 20181215 (Red Hat 8.2.1-6)
In file included from /usr/include/termios.h:25,
from kilo.c:40:
/usr/include/features.h:184:3: warning: #warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE" [-Wcpp]
^~~~~~~
kilo.c: In function ‘editorRefreshScreen’:
kilo.c:953:19: warning: implicit declaration of function ‘time’; did you mean ‘tee’? [-Wimplicit-function-declaration]
if (msglen && time(NULL)-E.statusmsg_time < 5)
^~~~
tee
I get an error when kilo is compiled, but why does it work. Why is that?
The compiler I are using is Clang
after git clone and trying to compile it's showing
In file included from /usr/include/termios.h:25,
from kilo.c:40:
/usr/include/features.h:185:3: warning: #warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE" [-Wcpp]
185 | # warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
| ^~~~~~~
kilo.c: In function ‘editorRefreshScreen’:
kilo.c:953:19: warning: implicit declaration of function ‘time’ [-Wimplicit-function-declaration]
953 | if (msglen && time(NULL)-E.statusmsg_time < 5)
| ^~~~
When characters like "ä", "ö" or "å" are typed in the editor, the showable cursor shifts 2 times to the right (one time more than it should). However, the characters typed after this will not appear right before the showable cursor, but two or more columns to the left depending on how many non-English characters you have typed. I didn't investigate what was causing this problem.
The terminios.h dependency requires cygwin is there any plan on changing this?
I use make to build the program, and found this problem:
$ make
cc -o kilo kilo.c -Wall -W -pedantic -std=c99
kilo.c: In function ‘editorUpdateRow’:
kilo.c:567:21: error: ‘UINT32_MAX’ undeclared (first use in this function); did you mean ‘__UINT32_MAX__’?
if (allocsize > UINT32_MAX) {
^~~~~~~~~~
__UINT32_MAX__
kilo.c:567:21: note: each undeclared identifier is reported only once for each function it appears in
Makefile:4: recipe for target 'kilo' failed
make: *** [kilo] Error 1
$ uname -a
Linux beaglebone 4.9.78-ti-r94 #1 SMP PREEMPT Fri Jan 26 21:26:24 UTC 2018 armv7l GNU/Linux
Just include <stdint.h>
and the program compiled correctly with no errors.
kilo
on MacOSI found no erorr when compiling.
And my environment on MacOS is:
$ uname -a
Darwin unknowntpo.local 19.4.0 Darwin Kernel Version 19.4.0: Wed Mar 4 22:28:40 PST 2020; root:xnu-6153.101.6~15/RELEASE_X86_64 x86_64
I am reading kilo code for learning C language.
In line551, allocating 9 bytes memory for one non-printable character.
Why 9 bytes memory need for a non-printable character?
And nonprint
variable is only initialized to 0 at line 543, and it is not assigned to other value.
So I think nonprint
is not useful at line 551.
What's the meaning of this?
row->render = malloc(row->size + tabs*8 + nonprint*9 + 1);
When trying to do ./kilo clip.c
, I get zsh: segmentation fault kilo clip.c
. View clip.c.
macOS 11.2.2, on x86_64 Intel CPU, compiled with clang and gcc (tried both, tried all optimisation levels), using default terminal and zsh.
Will try on linux and update.
EDIT 1 :
Possibly due to one/some of the errors posted in #77 .
Most likely:
kilo.c:1211:9: warning: 'break' will never be executed [-Wunreachable-code-break]
break;
kilo.c:1307:12: warning: 'return' will never be executed [-Wunreachable-code-return]
return 0;
or some other UB / EB ?
EDIT 2:
It does not instantly seg fault. It takes quite a few seconds. And it seems to use the CPU highly during this.
it is read and writing Gigabytes of data during this ! WTF
This is vanilla kilo
, with only one patch:
diff --git a/kilo.c b/kilo.c
index 9490a77..207e297 100644
--- a/kilo.c
+++ b/kilo.c
@@ -37,6 +37,7 @@
#define _BSD_SOURCE
#define _GNU_SOURCE
+#include <time.h>
#include <termios.h>
#include <stdlib.h>
t.c
beforehand, so that the syntax highlighting is enabled.Backtrace shows that we die with bogus row
values:
Core was generated by `./kilo t.c'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000401462 in editorRowHasOpenComment (row=0x16c5e90) at kilo.c:361
361 - 41 if (row->hl && row->rsize && row->hl[row->rsize-1] == HL_MLCOMMENT &&
(gdb) bt
#0 0x0000000000401462 in editorRowHasOpenComment (row=0x16c5e90) at kilo.c:361
#1 0x00000000004015fd in editorUpdateSyntax (row=0x16c5620) at kilo.c:395
#2 0x0000000000401da7 in editorUpdateRow (row=0x16c5620) at kilo.c:566
#3 0x00000000004024e5 in editorRowAppendString (row=0x16c5620,
s=0x16c4a70 "", len=0) at kilo.c:668
#4 0x00000000004028ea in editorDelChar () at kilo.c:750
#5 0x0000000000403d6c in editorProcessKeypress (fd=0) at kilo.c:1197
#6 0x0000000000403f5c in main (argc=2, argv=0x7fffaf038a38) at kilo.c:1270
(gdb) p row->size
$1 = 1819440195
(gdb) p row->rsize
$2 = 1025525293
(gdb)
Suspect, given the nature of the crash that editorRowDelChar, or editorDelRow are to blame. Either the memcpy is going wrong such that the size become pointers, or something else is corrupting them.
"Video" here for a clearer picture - https://asciinema.org/a/6ou2c1lmofuzedery5mjx7kf3
This is affecting my fork in a different way, but we'll keep that quiet.
t.c.txt
In editorUpdateRow, a check is never performed to find non-printable characters. However, the function should replace non-printable characters with '?'.
Hi,
Kilo appears to have a heap buffer overflow triggered by the memcmp
call on line 475 of kilo.c:
int j;
for (j = 0; keywords[j]; j++) {
int klen = strlen(keywords[j]);
int kw2 = keywords[j][klen-1] == '|';
if (kw2) klen--;
if (!memcmp(p,keywords[j],klen) &&
The signature of memcmp
:
int memcmp(const void *s1, const void *s2, size_t n);
memcmp
operates under the assumption that s1
and s2
are at least n
bytes long each.
This assumption clearly holds for the second argument (keywords[j]
), but there are no checks in place that makes sure that this assumption holds also for the first argument (p
).
The heap overflow can be verified by compiling kilo
with ASAN enabled:
$ git clone https://github.com/antirez/kilo.git
$ cd kilo
$ clang -fsanitize=address -o kilo kilo.c
$ ./kilo kilo.c
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60300000dcd5 at pc 0x0001029f16f9 bp 0x7fff5d233440 sp 0x7fff5d232c00
READ of size 6 at 0x60300000dcd5 thread T0
#0 0x1029f16f8 in wrap_memcmp (libclang_rt.asan_osx_dynamic.dylib+0x116f8)
#1 0x1029d08db in editorUpdateSyntax (kilo+0x1000048db)
#2 0x1029d18fb in editorUpdateRow (kilo+0x1000058fb)
#3 0x1029d1d86 in editorInsertRow (kilo+0x100005d86)
#4 0x1029d3c85 in editorOpen (kilo+0x100007c85)
#5 0x1029d6ae7 in main (kilo+0x10000aae7)
#6 0x7fff8956c5ac in start (libdyld.dylib+0x35ac)
#7 0x1 (<unknown module>)
0x60300000dcd5 is located 0 bytes to the right of 21-byte region [0x60300000dcc0,0x60300000dcd5)
allocated by thread T0 here:
#0 0x102a289c0 in wrap_malloc (libclang_rt.asan_osx_dynamic.dylib+0x489c0)
#1 0x1029d1303 in editorUpdateRow (kilo+0x100005303)
#2 0x1029d1d86 in editorInsertRow (kilo+0x100005d86)
#3 0x1029d3c85 in editorOpen (kilo+0x100007c85)
#4 0x1029d6ae7 in main (kilo+0x10000aae7)
#5 0x7fff8956c5ac in start (libdyld.dylib+0x35ac)
#6 0x1 (<unknown module>)
fatal error: termios.h: no such file or directory
Hey there, I don't really want to make a pull request here so I thought I'd just make an issue. Sorry if that's not proper etiquette! By the way, love the work.
The following lines of code appear in the source code:
erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
if (row) {
for (j = E.coloff; j < (E.cx+E.coloff); j++) {
if (j < row->size && row->chars[j] == TAB) cx += 7-((cx)%8);
cx++;
}
}
Surely this should be simplified slightly to:
if (filerow < E.numrows) {
erow *row = &E.row[filerow];
for (j = E.coloff; j < (E.cx+E.coloff); j++) {
if (j < row->size && row->chars[j] == TAB) cx += 7-((cx)%8);
cx++;
}
}
It seems a little off to make the conditional based off of the result of another conditional that appears literally one line above.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.