Amikor a gcc generál BoF-ot a binárisba. Bele.

 ( Pontscho | 2014. február 6., csütörtök - 11:50 )

Adott egy függvény definíció meg egy gcc472/darwin@x86_64, -O0 és néhány ősz hajszál.

void * cli_set_variable(char *var, char *val)
{
     return 0x00ff00ff00ff00ff;
}

…

      void *ret = cli_set_variable(var, val);
      vp_log(ret);

Fordítás és futtatás után az alábbi log bejegyzést dobja a gép:

20140206 113043a [97002] [0000000111965000] [var:task.c:276:master_start_task()] ret: 0xff00ff

Némi debuggolás után:

0x000000010c17cf3b <master_start_task+1989>:	mov    $0x0,%eax
0x000000010c17cf40 <master_start_task+1994>:	callq  0x10c177e13 <cli_set_variable>
0x000000010c17cf45 <master_start_task+1999>:	cltq   
0x000000010c17cf47 <master_start_task+2001>:	mov    %rax,-0x48(%rbp)

CLTQ:

cltq sign extends %eax to %rax

És így lesz egy pointer előjel bővítve… KÖSZI SRÁCOK. Vajon hány ilyen gcc által generált BoF leledzhet szerte a világon mindenféle szolgáltatásba és egyéb szoftverbe kódoltan?

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Mi ez a BoF? (ahhoz rövidnek tűnik, hogy a google válaszoljon)

Buffer overflow.

---
pontscho / fresh!mindworkz

Köszi. Más kérdés, hogy pointert ritkán szokás hexa konstansként visszaadni ;)
(ez valami egyéb probléma modellje volt vagy eleve konstanssal próbáltad?)

Köszi. Más kérdés, hogy pointert ritkán szokás hexa konstansként visszaadni ;)

Valoban, de nem pelda nelkul allo esemeny. Legutobb iOS4 kamera kernel modul revengnel volt szuksegem OS fuggoen egy belepesi pont ilyesfele hasznalatara, mert egy adott allokacios fuggenyt maskepp nem lehetett elerni. De ez most csak test pattern.

---
pontscho / fresh!mindworkz

OK, ez most teszt: nem lehet, hogy a gcc a konstans miatt fordított hülyeséget? (találgatok, ezért kérdeztem, hogy volt-e valami konkrét előzménye)
Sajnos a mostani processzorok assembly-je távol áll tőlem (meg már azok is, amiket anno kívül-belül ismertem :( ), szóval ebből az egészből csak a C kódot fogtam fel, meg azt próbáltam, amit írtál. Az assembly részt akár kínai írásjelekkel is felvéshetted volna. ;)

Azért ezt a pár utasítást nem olyan nehéz felfogni, még nekem is ment. ;) (Igaz én azért pislogtam, mert a gcc assembly dialektusában az utolsó paraméter a cél és nem az első, én meg azt szoktam meg.) A mov az csak megy, a callq is, a cltq-t leírta Pontscho, más meg nincs. A %rax az a 64 bites A regiszter, az %eax ennek az alsó 32 bitje, az %rbp a stack bázis pointer. Kihagytam valamit?

Azert a cast-ot illene odairni, mas kerdes, hogy a fordito is warningol ettol a konverziotol. Erdemes lenne a szabvanyban megnezni, hogy mit mond erre, de nem feltetlen a gcc-t okolnam ebben az esetben, hogy hulyeseg lett az eredmeny.

Dump of assembler code for function cli_set_variable:
0x0000000100008ec3 <cli_set_variable+0>:	push   %rbp
0x0000000100008ec4 <cli_set_variable+1>:	mov    %rsp,%rbp
0x0000000100008ec7 <cli_set_variable+4>:	mov    %rdi,-0x8(%rbp)
0x0000000100008ecb <cli_set_variable+8>:	mov    %rsi,-0x10(%rbp)
0x0000000100008ecf <cli_set_variable+12>:	mov    $0xff00ff00ff00ff,%rax
0x0000000100008ed9 <cli_set_variable+22>:	pop    %rbp
0x0000000100008eda <cli_set_variable+23>:	retq   

Ezert nem fontos a returnre adott gcc warning.

---
pontscho / fresh!mindworkz

de memoriacim konstanst azert akkor illik unsignedkent (unsigned long) megadni

--
NetBSD - Simplicity is prerequisite for reliability

buffer overflow

Ez nem bof, pont az ellenkezoje, raadaskent a castolas lemaradt amiert biztos sirt erosen a fordito.
Jah -> return (void*)0x00ff00ff00ff00ff;

Nem ertem miert gondoljak az emberek, hogy a fordito hulyeseget pofazik. Az a fordito pont azert warning-ol, mert faszsagot csinalsz, vagy ha nem faszsagot csinalsz, akkor szarul csinalod.


// Happy debugging, suckers
#define true (rand() > 10)

0x0000000100008ecf <cli_set_variable+12>:	mov    $0xff00ff00ff00ff,%rax

Emiatt a sor miatt erdektelen az, h mit rinyal a return-re a compiler. A problemam _nem_ ebbol fakadt es _nem_ itt volt. A mov magasrol tesz arra, h milyen elojele van vagy nincs egy adott konstansnak, a regiszter szinten nem foglalkozik vele. Az elojel csak az adat ertelmezesetol fuggo kerdes.

BoF-nak meg azert BoF, mert egyeb mas cim (eredetileg egy lefoglalt memoria terulet kerult returnre) elojellel kiegeszitve tok random helyre mutato pointer lesz belole.

---
pontscho / fresh!mindworkz

Bocsánat, a void* milyen típus is pontosan?

Nem arról szól a dolog -- persze, tévedhetek is -- hogy a definíciónál még nem lehet tudni, hogy milyen típust is ad vissza a függvény, viszont a függvény meghívásánál illene erről egy cast-olással nyilatkozni?

G.

============================================
"Share what you know. Learn what you don't."

Lényegében de, viszont itt nem a void-on, hanem a mögötte lévő *-on van a hangsúly. Ő a kódjában int-et cast-ol void*-ra


// Happy debugging, suckers
#define true (rand() > 10)

S akkor a konverzió int-ről void*-ra és void*-ról int-re automatikusan megy végbe, vagy asszimetrikus a dolog?

G.
============================================
"Share what you know. Learn what you don't."

Majdnem. A gond az volt, h egy kedves huzaskent a void*-ot lekasztolta nekem int-re es ezert nott oda a cltq.

---
pontscho / fresh!mindworkz

Ne haragudj, k.rég volt amikor C-t és egyéb, 3. generációs nyelveket tanulgattam, ezért is nem mertem leírni, de ahogy elnézem, mások is valami hasonló nézeten vannak: van egy függvényed, ami visszaad valamilyen típust. Van a returnben egy konstans értéked. Ilyenkor a fordító azt csinálja, hogy kitalál a konstans értékhez egy általa feltételezett típust, azt konvertálja a függvényed visszatérési értékeként megjelölt típusra és így adja vissza a returnbe írt értéket. (feltéve, hogy a mai fordítók is nagyjából úgy működnek e téren, mint húsz évvel ezelőtti társaik)

Végeredményben ez a szokásos: "a számítógép nem a kívánságaid, hanem az utasításaid szerint működik" esetének tűnik.
De összeszedem magam, hogy megpróbáljak valami assembly listát kipréselni a gcc-ből (remélem, még lehet :D) és megnézem, hogy különböző variációkra hogy reagál.
Régen imádtam ilyenekkel szórakozni, sajnos nagyon kiestem belőle. :(

Leginkabb azert rugoztok ezen a jelentektelen aprosagon, mert nem igazan ertitek, h hogyan mukodik a cdecl calling convention x86-on.
Egyszeru adat tipusok visszateresi ertekei igy alakulnak x86_64-en:

[unsigned] char : al
[unsigned] short : ax
[unsigned] int : eax
[unsigned] long: rax
float : st(0)
double : st(0)
void * : rax
akarmi * : rax

A topic inditoban leirt fuggveny x86_64 asm kodja innentol annyi, h mov $0xff00ff00ff00ff,%rax. Igy tok mindegy mit mire kellene castolni, attol meg minden esetben az elobbi asm kodra fog lefordulni a kod. Ha unsigned long lenne a fuggveny visszateresi ertekenek tipusa akkor is, ha long lenne, akkor is, ha int *, akkor is, ha void *, akkor is. A fuggveny visszateresi erteke 64 bites szamok eseten (ami lehet egy konstans, egy pointer, barmi) a rax-ban lesz megtalalhato, az ebben talalhato szam ertelmezese csak a kod tovabbi reszetol fugg. Ami viszont a fuggveny jo es normalis mukodese utan szepen a cltq-nak koszonhetoen ugy lett kezelve, mintha egy szimpla int lett volna a visszateresi ertek tipusa egy pointer helyett, teljesen fuggetlenul attol, h en ott egy allokalt memoriaterulet cimet vagy egy konstanst adok vissza.

---
pontscho / fresh!mindworkz

Igen, ertem en hogy egy cltq -val tobb lett, de azt tessek mar megerteni, hogy egy kibaszott signed int-et adtal neki, hogy konvertalja void*-ra. Megtette, szar is lett, de errol nem a gcc tehet, hanem te.


// Happy debugging, suckers
#define true (rand() > 10)

Attol meg, h folyamatosan ugyanazt a baromsagot szajkozod egyre aggresszivabban meg nem lesz igaz. Ha vegre ertelmezned azt a kibaszott asm dumpot, h mire fordult le az a kibaszott fuggveny, akkor latnad is miert.

---
pontscho / fresh!mindworkz

Vagyis hulye vagyok en, hulye a fordito is mikor szol hogy integert adsz pointerkent (makes pointer from integer without a cast), es hulye mindenki mas is.
Ertem en, rendben:)
Az meg hogy erre forditotta, nem meglepo, mivel ez meg csak nem is kiszervezett konstans, nyugodtan optimalizalhatja forditas idoben


// Happy debugging, suckers
#define true (rand() > 10)

Nem értem mit nem értesz, még én is értem. :) Ha a castolás (hiánya) miatt van ott a cltq, akkor annak a függvény asm kódjában kellene szerepelnie, még a ret előtt. Miután megvolt a ret, akármi is van a %rax-ban, azt onnantól kezdve void *-ként kell kezelni, függetlenül attól, hogy inttel lett inicializálva. Azonban a gcc ügyesen berakott egy cltq-t a callq után, mintha a visszatérési érték int lenne, pedig nem.

Azért mert visszatérési értékként ő int-et adott, hibásan, majd stack pop után a másik oldal foghatja a fejét


// Happy debugging, suckers
#define true (rand() > 10)

Ez C, itt nincs dinamikus típus. Minden adatot úgy értelmezünk, ahogy adott ponton épp megadtuk a statikus típust. Ha a függvény void *-ot ad vissza, akkor az ott void *.

Ki beszelt dinamikus tipusrol? Void* meg csak nem is tipus


// Happy debugging, suckers
#define true (rand() > 10)

Amikor arról beszéltél, hogy a callq után a kapott adat egy int, akkor feltételeztem úgy gondolod, hogy a fordító megjegyezte, hogy milyen típussal lett inicializálva az adat. Oké, ez nem teljesen az, mint ami pl. C++ esetén történik, de olyasmi.

Dehogynem típus, úgy hívják pointer.

Nem: a pointer az pointer, ami nem típus, a void pl típus, még ha tipizálatlan is. A pointer típus egy olyan pointer, aminek meg van határozva a típusa, pl char*.
Bovebben: http://en.wikipedia.org/wiki/C_data_types#Pointer_types


// Happy debugging, suckers
#define true (rand() > 10)

Jó persze, de a memóriában az összes pointer ugyanúgy néz ki, és mivel a memóriában reprezentálva van, ebből következően mégiscsak valamiféle típus.

A memóriában minden ugyanúgy néz ki, csupa egy és nulla:D


// Happy debugging, suckers
#define true (rand() > 10)

Ugye az megvan, h mivel regiszterben jon vissza a visszateresi ertek, ezert a regiszter pop szart se szamit ? :)

---
pontscho / fresh!mindworkz

Tenyleg ennyire problemas a szovegertes ? :) Nem az a problemam, h beszol a compiler, h konstanst adok vissza egy pointernel es ezert warningol, amiben igaza is van (mellesleg ha lekasztolom void *-ra, akkor meg warning sincs), hanem h utana kurta el a tortenetet. Gondolom az is megvolt, h szandekosan -O0-lal forditottam.

Csak a kedvedert:

aeryn:asd pontscho$ cat main.c
void *basz(void)
{
	return 1;
}
aeryn:asd pontscho$ gcc -c -o main.o main.c -O0 -Wall
main.c: In function 'basz':
main.c:4: warning: return makes pointer from integer without a cast
aeryn:asd pontscho$ otool -tv main.o
main.o:
(__TEXT,__text) section
_basz:
0000000000000000	pushq	%rbp
0000000000000001	movq	%rsp,%rbp
0000000000000004	movq	$0x0000000000000001,%rax
000000000000000e	movq	%rax,0xf0(%rbp)
0000000000000012	movq	0xf0(%rbp),%rax
0000000000000016	movq	%rax,0xf8(%rbp)
000000000000001a	movq	0xf8(%rbp),%rax
000000000000001e	popq	%rbp
000000000000001f	ret
aeryn:asd pontscho$

---

aeryn:asd pontscho$ cat main.c
void *basz(void)
{
	return (void *)1;
}
aeryn:asd pontscho$ gcc -c -o main.o main.c -O0 -Wall
aeryn:asd pontscho$ otool -tv main.o
main.o:
(__TEXT,__text) section
_basz:
0000000000000000	pushq	%rbp
0000000000000001	movq	%rsp,%rbp
0000000000000004	movq	$0x0000000000000001,%rax
000000000000000e	movq	%rax,0xf0(%rbp)
0000000000000012	movq	0xf0(%rbp),%rax
0000000000000016	movq	%rax,0xf8(%rbp)
000000000000001a	movq	0xf8(%rbp),%rax
000000000000001e	popq	%rbp
000000000000001f	ret
aeryn:asd pontscho$

---

aeryn:asd pontscho$ cat main.c 

#include 

void *basz(void)
{
	return malloc(1);
}

aeryn:asd pontscho$ gcc -c -o main.o main.c -O0 -Wall
aeryn:asd pontscho$ otool -tv main.o
main.o:
(__TEXT,__text) section
_basz:
0000000000000000	pushq	%rbp
0000000000000001	movq	%rsp,%rbp
0000000000000004	subq	$0x10,%rsp
0000000000000008	movq	$0x0000000000000001,%rax
0000000000000012	movq	%rax,%rdi
0000000000000015	callq	0x0000001a
000000000000001a	movq	%rax,0xf0(%rbp)
000000000000001e	movq	0xf0(%rbp),%rax
0000000000000022	movq	%rax,0xf8(%rbp)
0000000000000026	movq	0xf8(%rbp),%rax
000000000000002a	addq	$0x10,%rsp
000000000000002e	popq	%rbp
000000000000002f	ret
aeryn:asd pontscho$ 

Capisce? Szoval hagyjuk mar ezt a faszsagot es kezdjunk mar el gondolkodni is vegre.

---
pontscho / fresh!mindworkz

Gondolkodtam, es el van kurva sok minden;)


// Happy debugging, suckers
#define true (rand() > 10)

Nehez annyit mondani, h "bocs, beneztem egy kicsit" amire en csak annyit mondanek, h "ugyan, semmi gond, mindenkivel elofordul" ? :)

---
pontscho / fresh!mindworkz

Bocs, beneztem egy kicsit, csak tenyleg felreertheto amit irtal, viszont ha forditgatok magamnak akkor leesett volna:)


// Happy debugging, suckers
#define true (rand() > 10)

No prob, neztem mar be ennel durvabban is dolgokat. :)

---
pontscho / fresh!mindworkz

Vagyis hulye vagyok en, hulye a fordito is mikor szol hogy integert adsz pointerkent (makes pointer from integer without a cast), es hulye mindenki mas is.

Sot, mondok jobbat, "tortem" anno ugy darwin/arm-on kamera modult, h indirekt cimzessel, egy fuggveny pointernek beirtam egy konstanst es utana azt hivogatta utana a kod. :-)

makes pointer from integer without a cast

Lasd a keretes bejegyzest.

nyugodtan optimalizalhatja forditas idoben

Nem optimalizalta ki mar a BaT altal irtak es a -O0 miatt sem. :)

---
pontscho / fresh!mindworkz

Ebbol gondoltam a "kioptimalizalta" reszt:

return 0x00ff00ff00ff00ff;
mov $0xff00ff00ff00ff,%rax


// Happy debugging, suckers
#define true (rand() > 10)

Az csak szam kiiras kulonbsegebol adodik, a leading nullakat csak azert szoktam kiirni, h vezesse a szemet.

0x00ff00ff00ff00ff (8 octet) == $0xff00ff00ff00ff (7 octet)

---
pontscho / fresh!mindworkz

Jaja, most esett le nekem is, csak annyit neztem hogy ez nem egyezik:D
Nah jol van, azt hiszem aludnom kene, mert agressziv es bamba vagyok, ez meg nem jo kombo mint ahogy a mellekelt abra is mutatja


// Happy debugging, suckers
#define true (rand() > 10)

Én inkább gyakorlati egyed vagyok, kipróbáltam: ubin (ubi64, mint32 bit) ugyan pofázik a gcc a casting hiánya miatt, de ha a return-be odaírsz egy (void *)0x...-t, a legyártott assembly kód bitről-bitre azonos marad.
Viszont azt a cltq-t sehol sem látom benne.

Viszont azt a cltq-t sehol sem látom benne.

Szerinted miert voltam ideges ? :)

---
pontscho / fresh!mindworkz

Én úgy értettem, hogy attól, hogy ott van. :)

Igen, attol. A fuggveny eredetileg allokalt egy memoria teruletet ami a visszateres utan fel lett szabaditva es a cltq-nak koszonhetoen sig11-et dobott, teljesen jogosan. :)

---
pontscho / fresh!mindworkz

Mondd, az a logbejegyzés nálad hogy keletkezik??
Debug program írja ki vagy te íratod ki printf-fel?
Utóbbi esetben milyen formátumot adtál meg?

Mint mondtam, rohadt rég foglalkoztam C-vel, akkor még nem volt 64 bit, sem "%32lx" formátum string... :)

update: Ja, hogy volt valami crash is... akkor nem szóltam, csak nem értem. Ugyanis nálam még normálisan működik (látszólag).

Nem relevans az a log fuggveny, mert meg elotte tortent a magia. Nem tudom milyen csillagallas kellett hozza, de osszejott.

---
pontscho / fresh!mindworkz

Csak azért kérdeztem, mert amikor printf("%32x\n",...); próbáltam kiíratni a visszaadott értéket, akkor ugyanígy, jóval rövidebb értéket kaptam a visszaadottnál. Aztán kicseréltem az x-et lx-re és máris csodát láttam. :)

Detto:D
Kicsit felrevezeto a post, de igy mar rajottem;)


// Happy debugging, suckers
#define true (rand() > 10)

O egy void* fgv.-t definialt, ami egy tipizalatlan Pointer. Ebben az esetben egy pointerrel kell visszaterni minden esetben (es itt most lenyegtelen hogy az a pointer egy int, char tipusu, vagy tipizalatlan void). Ezzel szemben, egy fixen hakolt integer-el ter vissza (a 0 integer, 0.0f float, a 0x0 meg egy hexakent definialt integer). Itt egyertelmu, hogy nem a gcc a hulye, leven szolt is neki, hogy nem szokas integerel pointerkent visszaterni


// Happy debugging, suckers
#define true (rand() > 10)

Nem! Te voltal az aki egy integer-t castoltal egy pointer-re, a gcc ezert sirt. A szam az mindig szam lesz gcc-ben, a 0x meg bazira szam reprezentalas hexadecimalisan, ami meg mindig integer, raadaskent signed.

Raadaskent pont ezert nem a GCC csinalja az invalid pointert, hanem te.


// Happy debugging, suckers
#define true (rand() > 10)

Gondolom ennek a hozzaszolasnak az ertelmezese es a c-ben hasznalt alap call conv. ismeretenek hianya akadalyoz meg abban, h ezen a marhasagon vegre tullepjunk.

---
pontscho / fresh!mindworkz

Vitadonto otlet: a clang minek forditja? ;)

Warningnak. :)

Sajnos mivel meloban futottam bele es haladni kell ezert inkabb maskepp lett megoldva a problema, nem volt idom szarra nyomozni, h abban a kodhalmazban mi volt a hiszti oka.

---
pontscho / fresh!mindworkz

El szabad mondani, hogy hogyan oldottad meg/kerülted ki a problémát?
_____________________________
Powered by 1,3,7-trimetilxantin

Clangom nincs, de van visual studio. :)

Kód:

#include <stdio.h>

void * cli_set_variable(char *var, char *val)
{
	return 0x00ff00ff00ff00ff;
}

int main(void)
{
	void *ret = cli_set_variable(NULL, NULL);

	printf("%p", ret);
	return 0;
}

Asm (aláhúzva a függvény és annak meghívása):

; Listing generated by Microsoft (R) Optimizing Compiler Version 18.00.21005.1 

include listing.inc

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

_DATA	SEGMENT
$SG3059	DB	'%p', 00H
_DATA	ENDS
PUBLIC	cli_set_variable
PUBLIC	main
EXTRN	printf:PROC
pdata	SEGMENT
$pdata$main DD	imagerel $LN3
	DD	imagerel $LN3+42
	DD	imagerel $unwind$main
pdata	ENDS
xdata	SEGMENT
$unwind$main DD	010401H
	DD	06204H
xdata	ENDS
; Function compile flags: /Odtp
_TEXT	SEGMENT
ret$ = 32
main	PROC
; File c:\users\bat\temp\test.c
; Line 9
$LN3:
	sub	rsp, 56					; 00000038H
; Line 10
	xor	edx, edx
	xor	ecx, ecx
	call	cli_set_variable
	mov	QWORD PTR ret$[rsp], rax
; Line 12
	mov	rdx, QWORD PTR ret$[rsp]
	lea	rcx, OFFSET FLAT:$SG3059
	call	printf
; Line 13
	xor	eax, eax
; Line 14
	add	rsp, 56					; 00000038H
	ret	0
main	ENDP
_TEXT	ENDS
; Function compile flags: /Odtp
_TEXT	SEGMENT
var$ = 8
val$ = 16
cli_set_variable PROC
; File c:\users\bat\temp\test.c
; Line 4
	mov	QWORD PTR [rsp+16], rdx
	mov	QWORD PTR [rsp+8], rcx
; Line 5
	mov	rax, 71777214294589695			; 00ff00ff00ff00ffH
; Line 6
	ret	0
cli_set_variable ENDP
_TEXT	ENDS
END

Eredmény:

C:\Users\BaT\Temp>cl test.c
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

test.c
test.c(5) : warning C4047: 'return' : 'void *' differs in levels of indirection from '__int64'
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj

C:\Users\BaT\Temp>test.exe
00FF00FF00FF00FF
C:\Users\BaT\Temp>

sub.

+1

Deklaráltad a cli_set_variable függvényt a hívás helye előtt? A generált kód alapján nem úgy néz ki...

Szerintem is ez lesz a gond: nem volt prototípus, 4-byte-os integert alaptértelmezett a fordító, azt terjesztette ki pointerré (azért előjelkiterjesztéssel, mert a programok 64-bites módban a címtartomány legalját és legtetejét szokták használni: lásd itt az ábrát)

Egyébként még a fv-ben is kitennék egy-két L-betűt:
return 0x00ff00ff00ff00ffLL;
sőt:
return (void *)0x00ff00ff00ff00ffLL;

PS: Mondjuk az is igaz, hogy jobb helyen addig nem is foglalkoznak egy ilyen "hibával", amig a fordítás nem warningmentes (nem, nem a warningok elnyomására gondolok, hanem a -W -Wall -Wextra -pedantic uniójára)

Egy kave kozben nekem is eszembe jutott ez, aztan kiderult, h igen.

---
pontscho / fresh!mindworkz

Akkor kérj elnézést a GCC-től, mert ezek szerint nem ők hibáztak!

Deklaráltad a cli_set_variable függvényt a hívás helye előtt?

Ugy ertem, igen, deklaráltam a függvényt a hívás helye előtt.

---
pontscho / fresh!mindworkz

Oh, félreértettem.

Mi volt végül a hiba oké? Csak nem PEBKAC?